这里有两个问题。首先,通过总是一个好主意CancellationToken
to the Task.Run
API,除了使其可用于任务的 lambda 之外。这样做会将令牌与任务关联起来,对于正确传播由任务触发的取消至关重要token.ThrowIfCancellationRequested
.
但这并不能解释为什么取消状态task1
仍然可以正确传播(task1.Status == TaskStatus.Canceled
),虽然它不适合task2
(task2.Status == TaskStatus.Faulted
).
现在,这可能是非常罕见的情况之一,其中巧妙的 C# 类型推断逻辑可能违背开发人员的意愿。讨论得很详细here https://stackoverflow.com/q/11941779/1768303 and here https://stackoverflow.com/q/24316189/1768303。总结一下,如果有task1
,以下覆盖Task.Run
由编译器推断:
public static Task Run(Func<Task> function)
而不是:
public static Task Run(Action action)
那是因为task1
lambda 没有自然的代码路径for
循环,所以它也可能是一个Func<Task>
拉姆达,尽管它不是async
它不会返回任何东西。这是编译器更喜欢的选项Action
。然后,使用这样的覆盖Task.Run
等价于:
var task1 = Task.Factory.StartNew(new Func<Task>(() =>
{
for (var i = 0; ; i++)
{
Thread.Sleep(i); // simulate work item #i
token.ThrowIfCancellationRequested();
}
})).Unwrap();
类型的嵌套任务Task<Task>
由返回Task.Factory.StartNew
,得到展开的 http://msdn.microsoft.com/en-us/library/ee795275%28v=vs.110%29.aspx to Task
by Unwrap()
. Task.Run
足够聪明 http://blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx当它接受时自动进行这样的解包Func<Task>
. 解包的 Promise 风格的任务正确地从其内部任务传播取消状态,作为OperationCanceledException
例外由Func<Task>
拉姆达。这不会发生在task2
,它接受一个Action
lambda 并且不会创建任何内部任务。取消不会被传播task2
, 因为token
尚未与task2
via Task.Run
.
最后,这可能是期望的行为task1
(当然不是为了task2
),但在任何一种情况下我们都不想在幕后创建嵌套任务。而且,这种行为对于task1
通过引入条件可能很容易被破坏break
出于for
loop.
正确的代码为task1
应该是这个:
var task1 = Task.Run(new Action(() =>
{
for (var i = 0; ; i++)
{
Thread.Sleep(i); // simulate work item #i
token.ThrowIfCancellationRequested();
}
}), token);