如何组合TaskCompletionSource和CancellationTokenSource?

ast*_*ker 17 c# asynchronous async-await cancellationtokensource taskcompletionsource

我有这样的代码(在这里简化)等待完成任务:

var task_completion_source = new TaskCompletionSource<bool>();
observable.Subscribe(b => 
   { 
      if (b) 
          task_completion_source.SetResult(true); 
   });
await task_completion_source.Task;    
Run Code Online (Sandbox Code Playgroud)

这个想法是订阅并等待true布尔流.这完成了"任务",我可以继续前进await.

但是我想取消 - 但不是订阅,而是等待.我想传递取消令牌(不知何故),task_completion_source所以当我取消令牌源时,await将继续前进.

怎么做?

更新:CancellationTokenSource这个代码是外部的,我这里所有的都是来自它的令牌.

Evk*_*Evk 20

如果我理解正确,你可以这样做:

using (ct.Register(() => {
    // this callback will be executed when token is cancelled
    task_comletion_source.TrySetCanceled();
})) {
    // ...
    await task_comletion_source.Task;
}
Run Code Online (Sandbox Code Playgroud)

请注意,它将在您的await上抛出异常,您必须处理该异常.

  • op 澄清了,你可能想要`CancellationToken 令牌;token.Register(...` 以更匹配他的代码。此外,您可能想要执行 `TrySetCanceled`(并使用 `TrySetResult`),这样如果任务已经完成,你就不会抛出异常。 (2认同)
  • 从另一个答案的评论来看,将“Register”调用的结果放入“using”块中以正确处理它可能是个好主意。我相信这应该可以避免提到的资源泄漏。 (2认同)
  • @ygoe 谢谢,完成。虽然我认为这是一个非常小的泄漏,但当然,如果可以避免它 - 应该避免它。 (2认同)

Ste*_*ary 8

我建议你不要自己构建它.取消令牌周围有很多边缘案例,这些案例很难完成.例如,如果Register从未处理从中返回的注册,则最终可能会导致资源泄漏.

相反,您可以使用Task.WaitAsync我的AsyncEx.Tasks库中的扩展方法:

var task_completion_source = new TaskCompletionSource<bool>();
observable.Subscribe(b => 
{ 
  if (b) 
    task_completion_source.SetResult(true); 
});
await task_completion_source.Task.WaitAsync(cancellationToken);
Run Code Online (Sandbox Code Playgroud)

另外,我强烈建议您使用ToTask而不是明确TaskCompletionSource.再次,ToTask很好地处理边缘情况.

  • @JustinSkiles:如果注册未处理(例如,使用接受的答案中的代码),*并且*如果您有一个长期存在的真实(可取消的)“CancellationToken”(即代表进程关闭),那么注册保持有效,TCS 也保持有效。如果此代码在进程运行期间多次执行,则每次运行时都会进行另一次注册,TCS 就会泄漏。 (2认同)