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

问题描述 投票:0回答:3

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

这两个案例在行为上看似相同,但其实不然。

案例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 Preview,如果这很重要的话,它可能使用 Roslyn 进行编译。

c# .net task-parallel-library async-await task
3个回答
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); });

第一个按预期创建任务。第二个创建一个任务,其返回类型是等待2000ms的任务。第一个示例之所以有效,是因为以下重载...

 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();

4
投票

不鼓励将

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<Task>(async () => await Task.Delay(2000));   
t.Unwrap().Wait();

使用

Task.Run
更简单,可以为您完成所有操作,包括
Unwrap
,因此您可以返回代表整个
Task
操作的
async
作为回报。


2
投票

Aron 的回答并非100%正确。如果您使用建议的修复,您最终会得到:

不能在 Promise 风格的任务上调用 Start。

就我而言,我通过保存对包装任务和未包装任务的引用解决了类似的问题。您可以使用

wrappedTask.Start()
启动任务,但使用
unwrappedTask.Wait()
等待任务(或访问与任务执行相关的任何内容,例如其
TaskStatus
)。

来源

© www.soinside.com 2019 - 2024. All rights reserved.