如果 async/await 没有创建新线程然后解释这段代码

If async/await doesn't create new thread then explain this code

我读过 this thread which claims with reference to msdn,认为 async/await 不会创建新线程。请看下面的代码:

static class Program
{
    static void Main(string[] args)
    {
        var task = SlowThreadAsync();
        for(int i = 0; i < 5; i++)
        {
            Console.WriteLine(i * i);
        }
        Console.WriteLine("Slow thread result {0}", task.Result);
        Console.WriteLine("Main finished on thread {0}", Thread.CurrentThread.ManagedThreadId);
        Console.ReadKey();
    }

    static async Task<int> SlowThreadAsync()
    {
        Console.WriteLine("SlowThreadAsync started on thread {0}", Thread.CurrentThread.ManagedThreadId);
        await Task.Delay(2000);
        Console.WriteLine("SlowThreadAsync completed on thread {0}", Thread.CurrentThread.ManagedThreadId);
        return 3443;

    }
}

作为这段代码的结果,我得到了不同的 ThreadId。为什么同一个线程得到不同的ThreadId?

您正在为示例使用控制台应用程序。这会极大地影响您的测试结果。控制台应用程序没有自定义 SynchronizationContext(就像 Winforms、WPF 和 ASP.NET 一样),因此它使用 ThreadPoolTaskScheduler 在任意线程池线程上安排延续。在 UI 应用程序中尝试这个相同的示例,您将看到在同一线程上调用的延续。

您链接的文章试图传达的是,调用异步方法并不能保证任何代码运行在单独的线程上。所以如果你想要保证这一点,你必须手动完成。特别是他们 不是 试图说异步方法将 总是 运行 在与调用方法 相同的线程上,因为在许多情况下,这显然是错误的。

我知道自问这个问题以来已经有一段时间了,但我 运行 解决了这个问题,并提供了有关 why/when 这种情况的更多详细信息。 await 的工作原理是,在等待的任务完成后,在线程池认为方便的任何线程上,将 await 之后的代码安排为 运行。通常这似乎与等待的任务在同一个线程上。不确定这与其他答案中提到的 SynchronizationContext 有何关系。

我注意到一个例外,当等待的任务很快完成时,似乎他们没有足够的时间将代码放在回调上,所以代码最终在第三个线程上被调用。