4

我有这样的代码(这里简化),它等待着完成任务:如何结合TaskCompletionSource和CancellationTokenSource?

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

的想法是认购和布尔值的流中等待true。这完成了“任务”,我可以继续前进await

但是我想取消 - 但不是订阅,而是等待。我想通过取消令牌(不知何故)到task_completion_source,所以当我取消令牌源时,await将继续。

怎么办?

更新CancellationTokenSource是此代码的外部,我在这里所有的是从它的令牌。

+0

'task_completion_source.SetCanceled'有什么问题?请注意,假设你正确地处理任务取消:) – Luaan

+0

@Luaan,这没什么错,但没有代码**运行**可以执行它。每个人都在等待什么 - 订阅等待数据(可能没有),“等待”等待任务完成。 – astrowalker

+0

如果通过canceltoken取消异步进程,它将触发一个TaskCanceledException,这反过来将结束等待(您需要处理异常)。 –

回答

8

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

CancellationToken ct; 
ct.Register(() => 
{ 
    // this callback will be executed when token is cancelled 
    task_comletion_source.TrySetCanceled(); 
}); 

注意,它会扔在你的await,你必须处理异常。

+1

该op说明,你可能想'CancellationToken令牌; token.Register(...'以更多地匹配他的代码,并且,你可能想要做'TrySetCanceled'(并且使用'TrySetResult'),所以如果任务已经完成,你不会抛出异常。 –

+0

非常感谢很多,令牌来源在这里不是问题,因为它不涉及(在原始编辑:-))。 – astrowalker

+0

@ScottChamberlain,啊,谢谢你提醒我试试......,我没有意外掉进那个圈套,但现在我不会因为你而坠落。 – astrowalker

3

我建议你不要自己创建它。有许多关于取消令牌的边缘情况,这些令牌很难正确地获取。例如,如果从Register返回的注册从不处理,则最终可能会发生资源泄漏。

相反,你可以使用Task.WaitAsync扩展方法从我AsyncEx.Tasks library

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

在一个侧面说明,我强烈建议您使用ToTask,而不是一个明确的TaskCompletionSource。再次,ToTask很好地处理你的边缘情况。

+0

谢谢,但当然我很好奇 - 你能提一下使用TaskCompletionSource(或指向它们)的边缘情况吗?到目前为止,我没有使用它的问题。 – astrowalker

+0

如果任务已完成,'Set *'将抛出。默认情况下,Set *'和TrySet *允许同步延续(在'await'可能在*'Subscribe'内*后的你的调用堆栈。默认情况下Task是允许子任务的。 –

+0

@StephenCleary,在什么情况下会出现这种边缘情况(注册未处理)? –