任务构造函数与具有异步操作的Task.Run - 不同的行为

时间:2015-01-21 00:29:52

标签: c# .net task-parallel-library async-await task

有人可以解释一下,也许我错过了一些明显的东西。

这两个案例的行为似乎相同,但事实并非如此。

案例1

  • 使用async Action,启动任务,该任务在一段时间内有效:

    var t = Task.Run(async () => { await Task.Delay(2000); });
    
  • 第二个任务等待第一个任务:

    var waitingTask = Task.Run(() => { t.Wait(); });
    
  • 等待第二项任务:

    waitingTask.Wait();
    

案例2

  • 使用Task构造函数构建Task,传递相同的async Action

    var t = new Task(async () => { await Task.Delay(2000); });
    
  • 启动另一项任务以等待第一项任务(就像第一种情况一样):

    var waitingTask = Task.Run(() => { t.Wait(); });
    
  • 开始第一项任务:

    t.Start();
    
  • 等待第二项任务:

    waitingTask.Wait();
    

第一种情况表现如预期:等待任务在第一种情况结束后,在2秒之后结束。

第二种情况很奇怪:等待任务很快就会结束,早在第一种情况发生之前

从两个任务打印消息时很容易看到。第二个任务结束时的打印将显示差异。

我正在使用VS 2015预览版,如果这很重要,可能会使用Roslyn进行编译。

3 个答案:

答案 0 :(得分:8)

答案在http://blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx

实际上问题在于new Task,并且只有Action,而您尝试提供Func<Task>。以下是前两行,但使用正确的重载进行了重写。

Task t = Task.Run(async () => { await Task.Delay(2000); });

Task<Task> t = new Task<Task>(async () => { await Task.Delay(2000); });

第一个按预期创建任务。第二个创建一个任务,谁的返回类型是一个等待2000毫秒的任务。第一个例子起作用,因为以下重载...

 public Task Run(Func<Task>);
 public Task<T> Run(Func<T>);
 public Task<T> Run(Func<Task<T>>);
 etc...

这些重载旨在为您自动调用Task<Task<T>>.Unwrap()

结果是在你的第二个例子中,你实际上正在等待第一个任务的开始/排队。

您可以通过

修复第二个示例
var t = new Task<Task>(async () => { await Task.Delay(2000); }).Unwrap();

答案 1 :(得分:3)

不建议Task使用async-await构造函数,您应该只使用Task.Run

var t = Task.Run(async () => await Task.Delay(2000));

Task构造函数是在[基于任务的异步模式(TAP)] [1]和async-await之前构建的。它将返回一个只启动内部任务并继续前进而不等待它完成的任务(即点火并忘记)。

您可以通过将所需结果指定为Task<Task>来解决此问题,并使用Unwrap创建代表整个操作的任务,包括内部任务:

var t = new Task(async () => await Task.Delay(2000));   
t.Unwrap().Wait();

使用Task.Run更简单,并为您完成所有这些操作,包括Unwrap,以便您返回代表整个Task操作的async

答案 2 :(得分:1)

Aron的回答不是100%正确。如果您使用建议的修补程序,最终会得到:

  

启动可能不会在承诺式任务上调用。

在我的情况下,我通过保持对包装和未包装任务的引用来解决类似的问题。您使用wrappedTask.Start()来启动任务,但unwrappedTask.Wait()等待它(或访问与任务执行相关的任何内容,例如TaskStatus)。

Source