我有这样的代码(这里简化了),等待完成任务:
var task_completion_source = new TaskCompletionSource<bool>();
observable.Subscribe(b =>
{
if (b)
task_completion_source.SetResult(true);
});
await task_completion_source.Task;
这样做的目的是订阅和等待true
在布尔人流中出现。这样就完成了“任务”,我可以在await
之外继续前进。
但是我想取消--但不是订阅,而是等待。我想将cancel令牌(以某种方式)传递给task_completion_source
,所以当我取消令牌源时,await
将继续。
该怎么做呢?
Update:CancellationTokenSource
位于此代码的外部,这里我所拥有的只是来自它的令牌。
发布于 2016-10-06 13:29:37
如果我对你的理解正确,你可以这样做:
using (cancellationToken.Register(() => {
// this callback will be executed when token is cancelled
task_comletion_source.TrySetCanceled();
})) {
// ...
await task_comletion_source.Task;
}
请注意,它将向您的等待抛出异常,您必须处理该异常。
发布于 2016-10-06 17:42:07
我建议你不要自己建这个。在取消令牌周围有许多边缘情况,这些情况很乏味,很难纠正。例如,如果从Register
返回的注册从未被释放,则可能导致资源泄漏。
相反,您可以从我的Task.WaitAsync
中使用 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
可以很好地处理边缘案例。
发布于 2018-07-27 20:01:44
这是我自己写这篇文章的尝试。我差点犯了个错误,没有处理登记册(多亏了斯蒂芬·克利里)
/// <summary>
/// This allows a TaskCompletionSource to be await with a cancellation token and timeout.
///
/// Example usable:
///
/// var tcs = new TaskCompletionSource<bool>();
/// ...
/// var result = await tcs.WaitAsync(timeoutTokenSource.Token);
///
/// A TaskCanceledException will be thrown if the given cancelToken is canceled before the tcs completes or errors.
/// </summary>
/// <typeparam name="TResult">Result type of the TaskCompletionSource</typeparam>
/// <param name="tcs">The task completion source to be used </param>
/// <param name="cancelToken">This method will throw an OperationCanceledException if the cancelToken is canceled</param>
/// <param name="timeoutMs">This method will throw a TimeoutException if it doesn't complete within the given timeout, unless the timeout is less then or equal to 0 or Timeout.Infinite</param>
/// <param name="updateTcs">If this is true and the given cancelToken is canceled then the underlying tcs will also be canceled. If this is true a timeout occurs the underlying tcs will be faulted with a TimeoutException.</param>
/// <returns>The tcs.Task</returns>
public static async Task<TResult> WaitAsync<TResult>(this TaskCompletionSource<TResult> tcs, CancellationToken cancelToken, int timeoutMs = Timeout.Infinite, bool updateTcs = false)
{
// The overrideTcs is used so we can wait for either the give tcs to complete or the overrideTcs. We do this using the Task.WhenAny method.
// one issue with WhenAny is that it won't return when a task is canceled, it only returns when a task completes so we complete the
// overrideTcs when either the cancelToken is canceled or the timeoutMs is reached.
//
var overrideTcs = new TaskCompletionSource<TResult>();
using( var timeoutCancelTokenSource = (timeoutMs <= 0 || timeoutMs == Timeout.Infinite) ? null : new CancellationTokenSource(timeoutMs) )
{
var timeoutToken = timeoutCancelTokenSource?.Token ?? CancellationToken.None;
using( var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancelToken, timeoutToken) )
{
// This method is called when either the linkedTokenSource is canceled. This lets us assign a value to the overrideTcs so that
// We can break out of the await WhenAny below.
//
void CancelTcs()
{
if( updateTcs && !tcs.Task.IsCompleted )
{
// ReSharper disable once AccessToDisposedClosure (in this case, CancelTcs will never be called outside the using)
if( timeoutCancelTokenSource?.IsCancellationRequested ?? false )
tcs.TrySetException(new TimeoutException($"WaitAsync timed out after {timeoutMs}ms"));
else
tcs.TrySetCanceled();
}
overrideTcs.TrySetResult(default(TResult));
}
using( linkedTokenSource.Token.Register(CancelTcs) )
{
try
{
await Task.WhenAny(tcs.Task, overrideTcs.Task);
}
catch { /* ignore */ }
// We always favor the result from the given tcs task if it has completed.
//
if( tcs.Task.IsCompleted )
{
// We do another await here so that if the tcs.Task has faulted or has been canceled we won't wrap those exceptions
// in a nested exception. While technically accessing the tcs.Task.Result will generate the same exception the
// exception will be wrapped in a nested exception. We don't want that nesting so we just await.
await tcs.Task;
return tcs.Task.Result;
}
// It wasn't the tcs.Task that got us our of the above WhenAny so go ahead and timeout or cancel the operation.
//
if( timeoutCancelTokenSource?.IsCancellationRequested ?? false )
throw new TimeoutException($"WaitAsync timed out after {timeoutMs}ms");
throw new OperationCanceledException();
}
}
}
}
如果在tcs获得结果或错误之前取消了TaskCanceledException,则抛出一个cancelToken。
https://stackoverflow.com/questions/39897151
复制相似问题