为什么此异步代码无法在等待之前的相同上下文中继续运行?

时间:2019-04-04 16:31:05

标签: c# .net asynchronous async-await configureawait

我正在努力了解Task.ContinueWith的工作方式。考虑以下代码:

private async void HandleButtonClick(object sender, EventArgs e)
{
    Console.WriteLine($"HandleButtonClick: a {GetTrdLabel()}");
    var t1 = Task.Run(() => DoSomethingAsync("First time"));
    Console.WriteLine($"HandleButtonClick: b {GetTrdLabel()}");
    await t1;
    Console.WriteLine($"HandleButtonClick: c {GetTrdLabel()}");

    var t2 = t1.ContinueWith(async (t) =>
    {
        Console.WriteLine($"t3: a {GetTrdLabel()}");
        Thread.Sleep(2000);
        Console.WriteLine($"t3: b {GetTrdLabel()}");
        await DoSomethingAsync("Second time");
        Console.WriteLine($"t3: c {GetTrdLabel()}");
    });

    Console.WriteLine($"HandleButtonClick: d {GetTrdLabel()}");
    await t2;
    Console.WriteLine($"HandleButtonClick: e {GetTrdLabel()}");
}

private async Task DoSomethingAsync(string label)
{
    Console.WriteLine($"DoSomethingElseAsync ({label}): a {GetTrdLabel()}");
    Thread.Sleep(2000);
    Console.WriteLine($"DoSomethingElseAsync ({label}): b {GetTrdLabel()}");
    await Task.Delay(2000);
    Console.WriteLine($"DoSomethingElseAsync ({label}): c {GetTrdLabel()}");
}

private string GetTrdLabel() => $"({Thread.CurrentThread.ManagedThreadId})";

输出如下。我的问题是关于突出显示的行:为什么第一个在await之后(即托管线程ID 3之后不继续在捕获的上下文中继续,因为我没有使用{{3} }?第二个 按预期进行,即线程ID 4

我觉得这与文档中的“ ... 尝试将连续性调回所捕获的原始上下文”(重点是我的)有关,但我不明白为什么 attempt 在第一种情况下失败。

.ConfigureAwait(false)

2 个答案:

答案 0 :(得分:2)

  

试图了解Task.ContinueWith的工作原理

最好忽略ContinueWith并改用await。但是,如果您想学习low-level, dangerous的做事方式,那么我将负责。请不要在生产中使用。

要注意的第一件事是ContinueWith始终将工作安排在任务计划程序中。默认情况下,它使用默认任务计划程序;默认情况下,它使用 current 任务计划程序。假设HandleButtonClick由您的UI框架直接调用(而不是例如通过任务计划程序进行计划),则当前没有任务计划程序,因此ContinueWith将使用默认任务计划程序,即线程池任务计划程序。为避免这种复杂的推理,代码应始终始终将TaskScheduler传递给ContinueWith

接下来要注意的是ContinueWith无法理解async的代表。就其而言,async lambda只是给它一个Func<Task>委托的一种好方法,而ContinueWith只关心该方法的初始同步部分。

最后要注意的是线程池线程被认为是可互换的。任何async / await代码都是如此。如果它在线程池上下文中运行并执行await,则它可以继续在任何线程池线程上执行。它可能与在await之前运行代码的线程不同。

答案 1 :(得分:-2)

如果在Windows应用程序中尝试相同的代码,您将得到不同的结果。 Windows应用程序的UI线程在不同的上下文中运行,并且控件在等待操作后返回到UI线程。控制台应用程序不是这种情况。