下面的代码开始并等待两个任务,task1
和task2
几乎完全相同。task1
不同于task2的是
它运行一个永无止境的循环。这两种情况对于执行CPU绑定工作的一些现实生活场景都非常典型。
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication
{
public class Program
{
static async Task TestAsync()
{
var ct = new CancellationTokenSource(millisecondsDelay: 1000);
var token = ct.Token;
// start task1
var task1 = Task.Run(() =>
{
for (var i = 0; ; i++)
{
Thread.Sleep(i); // simulate work item #i
token.ThrowIfCancellationRequested();
}
});
// start task2
var task2 = Task.Run(() =>
{
for (var i = 0; i < 1000; i++)
{
Thread.Sleep(i); // simulate work item #i
token.ThrowIfCancellationRequested();
}
});
// await task1
try
{
await task1;
}
catch (Exception ex)
{
Console.WriteLine(new { task = "task1", ex.Message, task1.Status });
}
// await task2
try
{
await task2;
}
catch (Exception ex)
{
Console.WriteLine(new { task = "task2", ex.Message, task2.Status });
}
}
public static void Main(string[] args)
{
TestAsync().Wait();
Console.WriteLine("Enter to exit...");
Console.ReadLine();
}
}
}
输出:
{ task = task1, Message = The operation was canceled., Status = Canceled }
{ task = task2, Message = The operation was canceled., Status = Faulted }
为什么状态task1
是Cancelled
,但状态task2
是Faulted
?请注意,在这两种情况下,我都不会将其token
作为第二个参数传递给Task.Run
。
发布于 2018-05-02 18:20:55
这里有两个问题。首先,传递CancellationToken
给Task.Run
API 总是一个好主意,除了可用于任务的lambda。这样做会将令牌与任务关联起来,对于正确传播由所触发的取消操作至关重要token.ThrowIfCancellationRequested
。
然而,这并不能解释为什么取消状态task1
仍然正确地传播(task1.Status == TaskStatus.Canceled
),而不是task2
(task2.Status == TaskStatus.Faulted
)。
现在,这可能是其中巧妙的C#类型推理逻辑可能违背开发者意愿的极少数情况之一。这里和这里详细讨论。总而言之,在task1
以下情况下,Task.Run
编译器推断以下重写:
public static Task Run(Func<Task> function)
而不是:
public static Task Run(Action action)
这是因为task1
lambda没有自然的代码路径for
,所以它可能是一个Func<Task>
lambda,尽管它不是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();
最后,这可能是一个理想的行为task1
(当然不是task2
),但我们不希望在任何情况下在场景后面创建嵌套任务。而且,task1
通过break
在for
循环中引入一个条件,这种行为很容易被破坏。
正确的代码task1
应该是这样的:
var task1 = Task.Run(new Action(() =>
{
for (var i = 0; ; i++)
{
Thread.Sleep(i); // simulate work item #i
token.ThrowIfCancellationRequested();
}
}), token);
https://stackoverflow.com/questions/-100008329
复制相似问题