在方法上使用await Task.Factory.StartNew将立即返回

时间:2018-06-22 07:08:15

标签: c# async-await console-application c#-7.1

我正在运行以下代码(C#7.1控制台应用程序),但我真的不明白为什么行为有所不同。

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

足够奇怪-如果我使用StartNew,但是在方法内部删除await,它将不会立即返回...

问题:这将立即返回:

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

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

没问题:不会立即返回

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

奇怪:这也不会立即返回:

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

WaitAndApologize:

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");
}

3 个答案:

答案 0 :(得分:7)

如果您使用Task.Factory.StartNew(MethodThatReturnsTask),则根据方法是否返回通用任务而返回Task<Task<T>>Task<Task>

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

  1. Task.Factory.StartNew产生一个调用MethodThatReturnsTask的任务,我们将此任务称为“任务A”
  2. 在您的情况下,
  3. MethodThatReturnsTask返回一个Task,我们称其为“任务B”,这意味着将使用处理该问题的StartNew的重载,最终结果是您取回包裹任务B的任务A。

要“正确地”等待这些任务需要2个等待,而不是1个。您的单个等待只是等待任务A,这意味着当它返回时,任务B仍在执行挂起的完成。

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

await await Task.Factory.StartNew(DisplayCurrentInfo);

但是,令人怀疑的是,为什么需要启动一个任务只是为了启动另一个异步方法。相反,您最好使用第二种语法,在该语法中,您只需等待该方法即可:

await DisplayCurrentInfo();

意见紧随其后:通常,一旦开始编写异步代码,就应在需要产生时保留使用Task.Factory.StartNew或其任何同级方法。线程(或类似的东西)来调用与其他内容不同步的内容。如果您不需要此特定模式,则最好不要使用它。

答案 1 :(得分:1)

await Task.Factory.StartNew(DisplayCurrentInfo);

第一个示例确实会立即返回,因为您正在创建一个新的Task并等待它,它将调用另一个任务,但是没有等待。考虑如下所示的伪代码,以保持等待状态。

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

在第三个示例中,WaitAndApologize未被等待,但是它被线程睡眠阻塞(全线程被阻塞)。

仅靠代码的帮助,我们不能说Task factory创建了一个新线程或仅在现有线程上运行。 如果它在同一线程中运行,则整个线程将被阻塞,因此您会感觉到代码正在await Task.Factory.StartNew(DisplayCurrentInfo);处等待,但通常不会发生。

如果它在其他网络线程上运行,您将不会获得与上述相同的结果。

答案 2 :(得分:0)

致电Task.Factory.StartNew(DisplayCurrentInfo);时使用的是签名:

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

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

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