Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is Task.Delay Worth Cancellation?

I've recently reimplemented a whole bunch of async WCF service methods using the cancellation pattern I've seen described in a number of places - where you await a Task.WhenAny on a started task and a Task.Delay. Of course, the existing tasks aren't cancellable, but that will hopefully be addressed in a later release.

In my case, the default duration of the Task.Delay is governed by a service setting. In an overwhelming majority of cases, the outcome is that the hoped-for task completes in the requisite amount of time. The setting is often made generous.

Most of (but not all) the examples I've seen do not bother to cancel the Task.Delay. Is it so cheap it's not worth worrying about? I know that cancellation raises an exception. If I cancel the delay, should I be handling the exception?

Here's the method I've made that all the service methods are calling through:

private async Task<T> GetOrTimeout<T>( Task<T> task, [CallerMemberName] string caller = "" )
{
  using ( var cts = new CancellationTokenSource( ) )
  {
    try
    {
      var timeout = GetDelay( cts.Token );
      var first = await Task.WhenAny( task, timeout );
      if ( first == timeout ) throw new TimeoutException( Properties.Resources.TimeoutOccurredInService.Fmt( caller ) );
      cts.Cancel( );  //--> haven't been doing this. Should I?
      return await task;
    }
    catch ( Exception ex )
    {
      throw LoggedFaultException( ex, caller );
    }
  }
}

...and the method that creates the delay would look like this:

private Task GetDelay( CancellationToken token )
{
  return Task
    .Delay( Properties.Settings.Default.ServiceMethodTimeout, token )
    .ContinueWith( _ => { }, TaskContinuationOptions.ExecuteSynchronously );
}

If I don't cancel the delay, am I holding onto resources way longer than necessary? In particular, I'm worried about the instances that WCF spins up to invoke the service methods. I'm worried they'll cut into the concurrency parameters that the service was configured with. The timeout setting is pretty coarse. It seems wasteful to not cancel, but this is all pretty new stuff to me.

Since cancellation involves exceptions, and since I've been trained to not use exceptions to communicate state, this feels like I've painted myself into some awful anti-pattern that I don't fully understand. Perhaps Task.Delay isn't the right choice for me. It feels like I've made it more complicated than I should. Any light shed on the situation would be most appreciated.

like image 698
Clay Avatar asked Sep 06 '15 18:09

Clay


2 Answers

First of all, this whole issue is probably negligible performance-wise and should only be considered otherwise after testing in a real environment.

However if we dive in, Task.Delay creates a task that is completed after a certain interval. It does so by creating a new System.Threading.Timer (which implements IDisposable) that completes the promise task after the interval using a ThreadPool thread.

If you use Task.Delay "a lot" you can have a considerable amount of wasted resources hanging around long after they're useful. If you also add any continuations to the Task.Delay task with a delegate that captures any references they too will hang around with no reason.

So yes, it's safer to cancel the task instead of letting it run out, though probably not by much.

like image 190
i3arnon Avatar answered Oct 10 '22 04:10

i3arnon


Task.Delay is worth cancelling when you care about the shutdown speed of your app.

One example is asp.net web applications.

When the server recycles your web-app (when it's being live-updated for example) it needs everything to end fast. If you have tasks waiting in the background, especially registered via QueueBackgroundObject or a similar technique, it might take a while for the app to shut down.

like image 40
Alex from Jitbit Avatar answered Oct 10 '22 04:10

Alex from Jitbit