β

C#中new Task中使用async lambda表达式后start的一个坑

炒饭的小站 27 阅读
浏览人数: 428

C#中的async/await/Task机制是个非常方便的功能,可以将异步的功能写成像同步一样易懂。在其背后,编译器做了一些脏活,比如将async方法从await的地方切开成多个方法,按顺序在线程池中运行。这其中也暗藏了一些坑,如果不是非常有经验的程序员可能就掉进去了。

比如如下的程序,会输出什么呢?

Task t = new Task(async () =>
{
    Console.WriteLine("Task t start");
    await Task.Delay(1000);
    Console.WriteLine("Task t end");
});
t.Start();
Task.Run(async () =>
{
    Console.WriteLine("Anonymous task start");
    await t;
    Console.WriteLine("Anonymous task end");
});
Thread.Sleep(1200);

这个程序开了两个Task,第一个等待1秒,第二个等待第一个,所以按一般的想法输出应该是:

Task t start
Anonymous task start  // 前两句顺序可能会变
// 1秒后
Task t end
Anonymous task end

然而,运行程序之后实际会得到:

Task t start
Anonymous task start  // 前两句顺序可能会变
Anonymous task end
// 1秒后
Task t end

这和想得不一样啊!后面那个Task根本就没等前一个运行完!然而,如果在后一个Task的最后加上代码检查 t.IsComplete 的话,会发现确实是 true ,所以不是后面这个Task的问题。

值得注意的是,前面那个Task使用了 new Task 来初始化,而不是常用的 Task.Run ,换成 Task.Run 之后,问题解决。真相只有一个,那就是 new Task 有问题!

查看 new Task 的方法签名,实际使用的是:

public Task(Action action);

Task.Run 实际使用的是:

public static Task Run(Func<Task> function);

谜团到此解开。对于async的无参数无返回值lambda表达式来说,实际上这个表达式的类型是 Func<Task> ,也就是返回 Task 类型,其中包括了这个Task必要的信息。虽然这个类型也兼容 Action ,就如同async方法可以返回 void ,但Task信息会丢失,运行后无法得知这个Task的运行进程,只能在第一个await前就认为它运行结束了,这就产生了最开始的结果。

为什么没有自动使用下面这个方法呢?

public Task(Func<Task> function);

因为 没有

解决方法就是,如果使用了async的lambda表达式,就不要再用 new Task 了,用 Task.Run 吧。

作者:炒饭的小站
随意而为,不拘一格

发表评论