Sen*_*ith 6 c# asynchronous task task-parallel-library taskcompletionsource
我发现我无法区分受控/合作与"不受控制"的任务/代表取消,而无需检查特定任务或代表背后的来源.
具体来说,我总是假设当OperationCanceledException从"低级操作"中捕获抛出时,如果引用的令牌无法与当前操作的令牌匹配,那么它应该被解释为失败/错误.这是它放弃(退出)的"低级操作"的声明,但不是因为你要求它这样做.
不幸的是,TaskCompletionSource无法关联一个CancellationToken作为取消的原因.因此,没有内置调度程序支持的任何任务无法传达其取消的原因,并且可能错误地将合作取消误报为错误.
更新:由于.NET 4.6 TaskCompletionSource 可以关联起来CancellationToken ,如果新的过载SetCanceled或TrySetCanceled使用.
例如以下内容
public Task ShouldHaveBeenAsynchronous(Action userDelegate, CancellationToken ct)
{
var tcs = new TaskCompletionSource<object>();
try
{
userDelegate();
tcs.SetResult(null); // Indicate completion
}
catch (OperationCanceledException ex)
{
if (ex.CancellationToken == ct)
tcs.SetCanceled(); // Need to pass ct here, but can't
else
tcs.SetException(ex);
}
catch (Exception ex)
{
tcs.SetException(ex);
}
return tcs.Task;
}
private void OtherSide()
{
var cts = new CancellationTokenSource();
var ct = cts.Token;
cts.Cancel();
Task wrappedOperation = ShouldHaveBeenAsynchronous(
() => { ct.ThrowIfCancellationRequested(); }, ct);
try
{
wrappedOperation.Wait();
}
catch (AggregateException aex)
{
foreach (var ex in aex.InnerExceptions
.OfType<OperationCanceledException>())
{
if (ex.CancellationToken == ct)
Console.WriteLine("OK: Normal Cancellation");
else
Console.WriteLine("ERROR: Unexpected cancellation");
}
}
}
Run Code Online (Sandbox Code Playgroud)
即使通过分发给所有组件的取消令牌请求取消,也会导致"错误:意外取消".
核心问题是TaskCompletionSource不知道CancellationToken,但是如果在"任务"中包含异步操作的"转到"机制无法跟踪这一点,那么我认为没有人可以指望它跨接口跟踪(图书馆的边界.
实际上TaskCompletionSource可以处理这个问题,但必要的TrySetCanceled重载是内部的,因此只有mscorlib组件才能使用它.
那么,是否有任何人都有一种模式,表明已在任务和代理边界"处理"取消?
郑重声明:是的,API 已损坏,因为 TaskCompletionSource 应该接受 CancellationToken。.NET 运行时修复了此问题以供自己使用,但在 .NET 4.6 之前并未公开该修复(TrySetCanceled 的重载)。
作为任务使用者,有两个基本选择。
所以像这样:
object result;
try
{
result = task.Result;
}
// catch (OperationCanceledException oce) // don't rely on oce.CancellationToken
catch (Exception ex)
{
if (task.IsCancelled)
return; // or otherwise handle cancellation
// alternatively
if (cancelSource.IsCancellationRequested)
return; // or otherwise handle cancellation
LogOrHandleError(ex);
}
Run Code Online (Sandbox Code Playgroud)
第一个依赖库编写者使用TaskCompletionSource.TrySetCanceled,而不是使用提供匹配标记的OperationCanceledException 执行TrySetException。
第二个不依赖库编写者“正确”地做任何事情,除了做任何必要的事情来处理他们的代码异常。这可能无法记录错误以进行故障排除,但无论如何都无法(合理地)从外部代码内部清除操作状态。
对于任务生产者来说,可以
后者很简单,但是像 Consumer 选项 2 一样,可能会忽略任务错误(甚至在执行序列停止之前很久就标记任务已完成)。
两者的完整实现(包括缓存委托以避免反射)...
更新:对于 .NET 4.6 及更高版本,只需调用TaskCompletionSource.TrySetCanceled接受CancellationToken. 当链接到 .NET 4.6 时,使用下面的扩展方法的代码将自动切换到该重载(如果使用扩展方法语法进行调用)。
static class TaskCompletionSourceExtensions
{
/// <summary>
/// APPROXIMATION of properly associating a CancellationToken with a TCS
/// so that access to Task.Result following cancellation of the TCS Task
/// throws an OperationCanceledException with the proper CancellationToken.
/// </summary>
/// <remarks>
/// If the TCS Task 'RanToCompletion' or Faulted before/despite a
/// cancellation request, this may still report TaskStatus.Canceled.
/// </remarks>
/// <param name="this">The 'TCS' to 'fix'</param>
/// <param name="token">The associated CancellationToken</param>
/// <param name="LazyCancellation">
/// true to let the 'owner/runner' of the TCS complete the Task
/// (and stop executing), false to mark the returned Task as Canceled
/// while that code may still be executing.
/// </param>
public static Task<TResult> TaskWithCancellation<TResult>(
this TaskCompletionSource<TResult> @this,
CancellationToken token,
bool lazyCancellation)
{
if (lazyCancellation)
{
return @this.Task.ContinueWith(
(task) => task,
token,
TaskContinuationOptions.LazyCancellation |
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default).Unwrap();
}
return @this.Task.ContinueWith((task) => task, token).Unwrap();
// Yep that was a one liner!
// However, LazyCancellation (or not) should be explicitly chosen!
}
/// <summary>
/// Attempts to transition the underlying Task into the Canceled state
/// and set the CancellationToken member of the associated
/// OperationCanceledException.
/// </summary>
public static bool TrySetCanceled<TResult>(
this TaskCompletionSource<TResult> @this,
CancellationToken token)
{
return TrySetCanceledCaller<TResult>.MakeCall(@this, token);
}
private static class TrySetCanceledCaller<TResult>
{
public delegate bool MethodCallerType(TaskCompletionSource<TResult> inst, CancellationToken token);
public static readonly MethodCallerType MakeCall;
static TrySetCanceledCaller()
{
var type = typeof(TaskCompletionSource<TResult>);
var method = type.GetMethod(
"TrySetCanceled",
System.Reflection.BindingFlags.Instance |
System.Reflection.BindingFlags.NonPublic,
null,
new Type[] { typeof(CancellationToken) },
null);
MakeCall = (MethodCallerType)
Delegate.CreateDelegate(typeof(MethodCallerType), method);
}
}
}
Run Code Online (Sandbox Code Playgroud)
和测试程序...
class Program
{
static void Main(string[] args)
{
//var cts = new CancellationTokenSource(6000); // To let the operation complete
var cts = new CancellationTokenSource(1000);
var ct = cts.Token;
Task<string> task = ShouldHaveBeenAsynchronous(cts.Token);
try
{
Console.WriteLine(task.Result);
}
catch (AggregateException aex)
{
foreach (var ex in aex.Flatten().InnerExceptions)
{
var oce = ex as OperationCanceledException;
if (oce != null)
{
if (oce.CancellationToken == ct)
Console.WriteLine("OK: Normal Cancellation");
else
Console.WriteLine("ERROR: Unexpected cancellation");
}
else
{
Console.WriteLine("ERROR: " + ex.Message);
}
}
}
Console.Write("Press Enter to Exit:");
Console.ReadLine();
}
static Task<string> ShouldHaveBeenAsynchronous(CancellationToken ct)
{
var tcs = new TaskCompletionSource<string>();
try
{
//throw new NotImplementedException();
ct.WaitHandle.WaitOne(5000);
ct.ThrowIfCancellationRequested();
tcs.TrySetResult("this is the result");
}
catch (OperationCanceledException ex)
{
if (ex.CancellationToken == ct)
tcs.TrySetCanceled(ct);
else
tcs.TrySetException(ex);
}
catch (Exception ex)
{
tcs.TrySetException(ex);
}
return tcs.Task;
//return tcs.TaskWithCancellation(ct, false);
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1009 次 |
| 最近记录: |