13

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.

i3arnon
  • 113,022
  • 33
  • 324
  • 344
Clay
  • 4,999
  • 1
  • 28
  • 45

2 Answers2

5

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.

i3arnon
  • 113,022
  • 33
  • 324
  • 344
  • @AlexeiLevenkov Why do you assume that's what it does? – i3arnon Sep 06 '15 at 19:04
  • you are right cancellation of the token passed to `Task.Delay` disposes the timer. I assumed that it behave the way regular tasks check for cancellation, which turned out not to be the case. – Alexei Levenkov Sep 06 '15 at 19:10
  • @i3arnon, thanks. Yes - I'm looking for a pattern to apply a lot. I'd hoped you'd look at this question as I saw the empty continuation in one of your answers previously. It seems as though this approach to timed-out tasks is becoming _common proactice_ if not _best practice_, and I am wondering if you think this is good, bad or otherwise. – Clay Sep 06 '15 at 21:25
  • 1
    @Clay Timing out using a delay task is the best you can do most of the time. I also prefer disposing of the instance if that's an option ([but people seem to disagree](http://stackoverflow.com/q/21468137/885318)) – i3arnon Sep 06 '15 at 21:50
1

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.

Alex from Jitbit
  • 53,710
  • 19
  • 160
  • 149
  • Do you mean that a pending `Task.Delay` task that has not been canceled, and has no continuation attached to it, can delay the shutdown of an application? – Theodor Zoulias Dec 21 '20 at 13:23
  • only IF the task has been created as a `IRegisteredObject` that is released once the task completes. If you're building a class library, you might not know how your task has been created, so better cancel all your delays once requested by a token – Alex from Jitbit Dec 21 '20 at 13:58
  • Alex the OP in their `GetOrTimeout` method are using a `Task.Delay` internally as a timeout mechanism. This task is not exposed through the API. The task returned by the `GetOrTimeout` is observing the internal `Task.Delay` only while the `Task task` argument is running, and is detached from it after that. Is you answer related to the OP's scenario, or to other possible uses of `Task.Delay`? – Theodor Zoulias Dec 21 '20 at 14:13
  • @TheodorZoulias the OP awaits the task. This happens in a private method but we don't know if that private methods is not exposed through another public method somewhere, and that public method can be called from a web-application, using a "registered" call (the one that prevents app shutdown until finished). Anyways, I'm answering a more general question - "are delays worth cancelling" - yes they are, in a web-app. Hope that's useful to someone. – Alex from Jitbit Dec 21 '20 at 14:37
  • Let's say that the `GetOrTimeout` method, as implemented by the OP, is registered by a web application. Do you think that commenting-out the `cts.Cancel();` statement may have the effect of delaying the shutdown of the web application? Sorry for insisting, but I think that it's important to clarify this point. The OP may become concerned after reading your answer, and it would be a pity if their concerns prove to be unjustified. – Theodor Zoulias Dec 21 '20 at 14:58