【问题标题】:Cancel task.delay without exception or use exception to control flow?无异常取消task.delay还是使用异常控制流程?
【发布时间】:2015-08-24 17:28:47
【问题描述】:

我不确定对我的代码中的事件做出反应的两种可能性。大多数情况下,我担心哪一个需要更少的资源。 我有一个注册到eventproducer 的观察者的方法。如果eventproducer返回了一些东西,方法退出,方法的调用者再次启动方法(你可以把它想象成一种长轮询)。

eventproducer 有时每秒会触发很多事件,有时会暂停几分钟。

第一种方法是等待 500 毫秒的延迟,然后检查是否有要返回的内容,否则(直到超时 5 分钟)再次延迟 500 毫秒。

eventProducer.RegisterListener((events)=>{evList.Add(events)});
while(evList.Count=0 && !TimeOut){
    await Task.Delay(500);}
eventProducer.UnRegister(...);
// return evList, method gets recalled immediately

第二种方法是使用CancellationToken。如果eventproducer 产生某些东西,CancellationTokenSource 会取消源。在方法中我等待Task.Delay(5min, cancellationToken)

eventProducer.RegisterListener((events)=>{evList.Add(events);
                                          cancelSource.Cancel();}
try
{
     await Task.Delay(5min, cancellationToken)
}
catch(TaskCanceledException){//nothing to do};
eventProducer.UnRegister(...);
// return evList, method gets recalled immediately

第二种方法的优点是,如果生产者产生了某些东西,该方法会立即返回,并且我们不必在循环中等待和唤醒。

但是对于第二种方法,每次生产者生产某些东西时,都会抛出一个TaskCanceledException。我担心这可能会影响系统负载,而不是唤醒和等待每 500 毫秒,尤其是当eventproducer 产生大量事件时。

我是否高估了抛出和捕获异常的成本?有没有办法用CancellationToken 取消Task.Delay 但不抛出TaskCanceledException? IE。类似task.setComplete

【问题讨论】:

  • 它只是“更长的时间”的占位符

标签: c# asynchronous exception-handling async-await task-parallel-library


【解决方案1】:

如果您想立即收到类似于取消通知的通知,但没有例外,您只需使用TaskCompletionSource

TaskCompletionSource 是您创建承诺任务的方式。您从Task 属性中获得一个未完成的任务,并使用SetResult 完成(或取消)它。您可以使用它来实际传递结果本身:

var tcs = new TaskCompletionSource<Events>();
eventProducer.RegisterListener(events => tcs.SetResult(events));

var result = await tcs.Task;
eventProducer.UnRegister(...);

此方案没有任何异常,不使用不必要的轮询


回答您的具体问题:

我是否高估了抛出和捕获异常的成本?

大概吧。您需要测试并证明这确实是一个问题。

有没有办法用CancellationToken 取消Task.Delay 但不抛出TaskCanceledException

是的。添加一个空的延续:

var delayTask = Task.Delay(1000, cancellationToken);
var continuationTask = delayTask.ContinueWith(task => { });
await continuationTask;

【讨论】:

  • 谢谢,TaskCompletionSource 看起来和我要找的完全一样!
  • 当使用“await Task.Delay(1000,cancellationToken).ContinueWith(task => { });”时,为什么不再抛出任务取消异常?在我对此进行 try-catch 之前,框架是否仍需要在内部处理 catch 和处理异常,或者它不会首先抛出异常?
  • @Snellface Task.Delay 任务仍然被取消,如果你等待它,它仍然会抛出异常。但你不是在等待它......你在等待一个什么都不做的延续。因为它什么都不做,也没有 CancellationToken,所以它不能抛出任何异常。
  • @i3arnon 所以使用 TaskCompletionSource 可能会更有效吗?我第一次听说它是在这个 QA 中,所以我对此相当陌生。 IMO 在语义上取消延迟而不是提前完成更符合语义,但是如果取消的成本比完成成本高得多,那么在这种特定情况下语义差异可以忽略不计。我认为任务必须在内部处理异常也可能是错误的,但这就是我的理解,只是当我不等待时,我不需要捕获它,对吧?
  • @Snellface 在这种情况下实际上没有例外.. 但对于Task.Delay 以外的其他情况可能会有。等待取消的任务会引发异常,但您不需要异常来创建取消的任务。想象一下Task.Delay 方法返回一个TaskCompletionSource,而不是调用tcs.SetResult,它使用tcs.SetCanceled。这样delayTask 就会被取消,等待它会抛出异常,而任务本身不会捕获任何异常。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-06-25
  • 1970-01-01
  • 1970-01-01
  • 2019-09-01
相关资源
最近更新 更多