在方法上使用 await Task.Factory.StartNew 会立即 return

Using await Task.Factory.StartNew on a method will return immediately

我是 运行 以下代码(C#7.1 控制台应用程序),我无法真正理解行为差异的原因。

如果我等待常规异步方法调用或 Task.Run - 它会按预期工作(即应用程序不会立即 return)。但是如果我使用 Task.Factory.StartNew - 它会立即 return 而实际上没有代码 运行.

奇怪的是 - 如果我使用 StartNew 但在方法内部删除 await,它不会立即 return...

问题:这 return 立即:

static async Task Main(string[] args)
{
    await Task.Factory.StartNew(DisplayCurrentInfo);
}

static async Task DisplayCurrentInfo()
{
    await WaitAndApologize();
    Console.WriteLine($"The current time is {DateTime.Now.TimeOfDay:t}");
    Thread.Sleep(3000);
}

即- 我看不到任何打印到控制台的内容,并且控制台已经关闭。

没问题:这不会return立即:

static async Task Main(string[] args)
{
    await DisplayCurrentInfo(); // or await Task.Run(DisplayCurrentInfo);
}

static async Task DisplayCurrentInfo()
{
    await WaitAndApologize();
    Console.WriteLine($"The current time is {DateTime.Now.TimeOfDay:t}");
    Thread.Sleep(3000);
}

奇怪:这也不return立即:

static async Task Main(string[] args)
{
    await Task.Factory.StartNew(DisplayCurrentInfo); 
}

static async Task DisplayCurrentInfo()
{
    WaitAndApologize();
    Console.WriteLine($"The current time is {DateTime.Now.TimeOfDay:t}");
    Thread.Sleep(3000);
}

等待并道歉:

static async Task WaitAndApologize()
{
    // Task.Delay is a placeholder for actual work.  
    await Task.Delay(2000);
    // Task.Delay delays the following line by two seconds.  
    Console.WriteLine("\nSorry for the delay. . . .\n");
}

如果您使用 Task.Factory.StartNew(MethodThatReturnsTask),您会返回 Task<Task<T>>Task<Task>,具体取决于该方法是否返回通用任务。

最终结果是你有2个任务:

  1. Task.Factory.StartNew 产生一个调用 MethodThatReturnsTask 的任务,我们称这个任务为 "Task A"
  2. MethodThatReturnsTask 在你的例子中 returns a Task,我们称它为 "Task B",这意味着使用了处理此问题的 StartNew 的重载最终的结果是你得到了一个包装了任务 B 的任务 A。

要"correctly"等待这些任务需要2个等待,而不是1个。您的单个等待只是等待任务A,这意味着当它returns时,任务B仍在执行等待完成。

要天真地回答您的问题,请使用 2 个等待:

await await Task.Factory.StartNew(DisplayCurrentInfo);

但是,值得怀疑的是,为什么您需要生成一个任务来启动另一个异步方法。相反,你最好使用第二种语法,你只需等待方法:

await DisplayCurrentInfo();

意见如下:一般来说,一旦您开始编写异步代码,就应该保留使用 Task.Factory.StartNew 或其任何兄弟方法,以备不时之需产生一个 thread (或类似的东西)来调用不与其他东西并行异步的东西。如果您不需要此特定模式,最好不要使用它。

当您调用 Task.Factory.StartNew(DisplayCurrentInfo); 时,您使用的是签名:

public Task<TResult> StartNew<TResult>(Func<TResult> function)

现在,由于您正在传递一个带有签名 Task DisplayCurrentInfo() 的方法,因此 TResult 类型是 Task

换句话说,您正在从 Task.Factory.StartNew(DisplayCurrentInfo) 返回一个 Task<Task>,然后您正在等待 returns Task 的外部任务 - 而您并没有在等待那个.因此它立即完成。

await Task.Factory.StartNew(DisplayCurrentInfo);

第一个示例立即执行 return,因为您正在创建一个新任务并等待它,这将调用另一个任务,但不会等待。考虑如下所示的 伪代码,以便继续等待。

await Task.Factory.StartNew( await DisplayCurrentInfo); //Imaginary code to understand

在第三个例子中 WaitAndApologize 不是 awaited,而是被线程睡眠阻塞(全线程被阻塞)。

仅在代码的帮助下,我们不能说任务工厂已经创建了一个新线程或只是 运行 在现有线程上。 如果它的 运行 在同一个线程中,整个线程都会被阻塞,所以你会感觉到代码正在等待 await Task.Factory.StartNew(DisplayCurrentInfo); ,但实际上它没有发生。

如果它 运行 在不同的线程上,您将不会得到与上面相同的结果。