任务延迟的取消是否值得?

14
我最近重新实现了一批使用异步WCF服务方法,采用了我在许多地方看到的取消模式——在启动任务和Task.Delay上等待Task.WhenAny。 当然,现有的任务是不可取消的,但希望这将在以后的版本中得到解决。
在我的情况下,默认的Task.Delay持续时间受服务设置的控制。 在绝大多数情况下,期望的任务都能在所需的时间内完成。该设置通常比较宽松。
我看到的大部分(但不是全部)示例都没有费心取消Task.Delay。 它如此便宜,不值得担心吗? 我知道取消会引发异常。 如果我取消延迟,是否应该处理异常?
以下是我制作的所有服务方法都在调用的方法:
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 );
    }
  }
}

而创建延迟的方法看起来会像这样:
private Task GetDelay( CancellationToken token )
{
  return Task
    .Delay( Properties.Settings.Default.ServiceMethodTimeout, token )
    .ContinueWith( _ => { }, TaskContinuationOptions.ExecuteSynchronously );
}

如果我不取消延迟,那么我是否会占用资源比必要的时间更长?特别是,我担心WCF生成来调用服务方法的实例。我担心它们会削弱配置为服务的并发参数。超时设置很粗糙。不取消似乎是一种浪费,但这对我来说都是新东西。

由于取消涉及异常,而且我被训练不要使用异常来传递状态,所以这感觉像是我陷入了某些可怕的反模式中,我并没有完全理解。也许Task.Delay不是适合我的选择。感觉上好像我把它弄得比应该的更复杂了。非常感谢您能为我解惑。

2个回答

6

首先,这整个问题在性能方面可能是微不足道的,只有在实际环境中进行测试后才应该考虑其他情况。

然而,如果我们深入研究,Task.Delay会创建一个任务,在一定时间间隔后完成。它通过创建一个新的System.Threading.Timer(实现了IDisposable)来完成承诺任务,使用ThreadPool线程在时间间隔之后完成。

如果您经常使用Task.Delay,您可能会有大量浪费的资源长时间挂起,而它们已经没有用处。如果您还将任何延续添加到带有捕获任何引用的委托的Task.Delay任务中,则它们也将无缘无故地挂起。

因此,是的,取消任务比让其运行完毕更安全,尽管可能差别不大。


@AlexeiLevenkov 为什么你认为它就是这样做的呢? - i3arnon
你是对的,传递给 Task.Delay 的令牌取消后会释放计时器。我以为它的行为方式与常规任务检查取消相同,但事实并非如此。 - Alexei Levenkov
@i3arnon,谢谢。是的 - 我正在寻找一个可以应用很多次的模式。我希望你能看一下这个问题,因为我之前在你的答案中看到了空续行符。如果不是最佳实践,这种处理超时任务的方法似乎正在变得越来越普遍,我想知道你认为这是好还是坏,或者其他什么意见。 - Clay
1
@Clay 大多数情况下,使用延迟任务来超时是最好的选择。如果可以的话,我也更喜欢处理实例(但似乎有人持不同意见)。 - i3arnon

2

如果你关心应用程序的关闭速度,那么取消Task.Delay是值得的。

一个例子是asp.net Web应用程序。

当服务器回收您的Web应用程序(例如进行实时更新时),它需要尽快结束所有操作。如果您有在后台等待的任务,特别是通过QueueBackgroundObject或类似技术注册的任务,应用程序可能需要一段时间才能关闭。


您的意思是一个未被取消且没有任何连续性附加的待处理Task.Delay任务可以延迟应用程序的关闭吗? - Theodor Zoulias
只有当任务被创建为“IRegisteredObject”并在任务完成后释放时,才能取消延迟。如果您正在构建一个类库,可能不知道如何创建您的任务,因此最好在令牌请求后取消所有延迟。 - Alex from Jitbit
Alex在他们的GetOrTimeout方法中使用了一个内部的Task.Delay作为超时机制。这个任务不会通过API暴露出来。由GetOrTimeout返回的任务只观察内部的Task.Delay,而Task<T> task参数正在运行时,它与其分离。你的回答是针对OP的情况还是针对Task.Delay的其他可能用途? - Theodor Zoulias
@TheodorZoulias,OP正在等待任务。这发生在一个私有方法中,但我们不知道该私有方法是否未通过其他公共方法公开,并且该公共方法可以使用“注册”调用从Web应用程序调用(该调用可防止应用程序关闭直到完成)。无论如何,我回答了一个更一般的问题 - “延迟取消是否值得” - 在Web应用程序中是值得的。希望对某人有所帮助。 - Alex from Jitbit
假设由OP实现的GetOrTimeout方法已被Web应用程序注册。您认为注释掉cts.Cancel();语句可能会延迟Web应用程序的关闭吗?很抱歉我一再强调,但我认为澄清这一点非常重要。在阅读您的答案后,OP可能会感到担忧,如果他们的担忧被证明是没有根据的,那将是遗憾的。 - Theodor Zoulias

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接