【问题标题】:If async/await doesn't create new thread then explain this code如果 async/await 没有创建新线程,请解释此代码
【发布时间】:2015-12-03 20:42:36
【问题描述】:

我读过this thread,它声称引用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?

【问题讨论】:

  • async/await 自己不创建任何线程。您开始的Task 可能会也可能不会创建线程。

标签: c# multithreading asynchronous async-await task-parallel-library


【解决方案1】:

我知道自从提出这个问题以来已经有一段时间了,但我遇到了这个问题,并想出了更多关于为什么/何时发生这种情况的详细信息。 await 的工作原理是在等待的任务完成后调度 await 之后的代码在线程池认为方便的任何线程上运行。通常这似乎与等待的任务在同一个线程上。不确定这如何与其他答案中提到的SynchronizationContext 一起使用。

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

【讨论】:

    【解决方案2】:

    您链接的文章试图传达的是调用异步方法并不能保证任何代码都在单独的线程上运行。因此,如果您确实想要保证这一点,则必须手动进行。特别是他们试图说异步方法将始终与调用方法在同一线程上运行,因为这在许多情况下是公然错误的。 p>

    【讨论】:

      【解决方案3】:

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

      【讨论】:

      • 我不认为ThreadPoolTaskScheduler 参与其中。在没有 s.context 的情况下,延续只是发生在底层TimerCallback 被调用的任何线程上(通过ThreadPool.UnsafeQueueUserWorkItem 安排)。
      • 我不同意,有一个很小但很重要的区别。在 WinForms 和 WPF 中,您都会收到来自Task.Result 的死锁。
      • @Aron 你不同意哪一部分? :)
      • @YuvalItzchakov,延续永远不会在 UI 应用程序上运行,因为它正忙于等待在 Task.Result 上完成延续 :)
      • @octavioccl 是的。如果没有捕获上下文,则将其调度到线程池中。