我最近在阅读有关 async/await 的文章,我对以下事实感到困惑:我正在阅读的许多文章/帖子都指出使用 async await (Example) 时不会创建新线程。
我创建了一个简单的控制台应用程序来测试它
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Main: " + Thread.CurrentThread.ManagedThreadId);
MainAsync(args).Wait();
Console.WriteLine("Main End: " + Thread.CurrentThread.ManagedThreadId);
Console.ReadKey();
}
static async Task MainAsync(string[] args)
{
Console.WriteLine("Main Async: " + Thread.CurrentThread.ManagedThreadId);
await thisIsAsync();
}
private static async Task thisIsAsync()
{
Console.WriteLine("thisIsAsyncStart: " + Thread.CurrentThread.ManagedThreadId);
await Task.Delay(1);
Console.WriteLine("thisIsAsyncEnd: " + Thread.CurrentThread.ManagedThreadId);
}
}
以下代码的输出是:
Main: 8
Main Async: 8
thisIsAsyncStart: 8
thisIsAsyncEnd: 9
Main End: 8
我是否错过了重点,或者 thisIsAsyncEnd 的线程 ID 与其他操作不同?
编辑:
我已经按照下面的答案中的建议更新了代码,以 await Task.Delay(1)
,但我仍然看到相同的结果。
引用下面的答案:
Rather, it enables the method to be split into multiple pieces, some of which may run asynchronously
如果没有创建其他线程,我想知道 asynchronously
部分在哪里运行?
如果它在同一个线程上运行,它不应该因为长时间的 I/O 请求而阻塞它,或者编译器足够聪明,可以在花费太长时间的情况下将该操作移动到另一个线程,并且毕竟使用了一个新线程?
最佳答案
我建议您阅读我的 async
intro发布以了解 async
和 await
关键字。特别是,await
(默认情况下)将捕获“上下文”并使用该上下文来恢复其异步方法。此“上下文”是当前 SynchronizationContext
(或 TaskScheduler
,如果没有 SynchronzationContext
)。
I want to know where does the asynchronously part run, if there are no other threads created? If it runs on the same thread, shouldn't it block it due to long I/O request, or compiler is smart enough to move that action to another thread if it takes too long, and a new thread is used after all?
正如我在我的博客上解释的那样,truly asynchronous operations do not "run" anywhere .在这种特殊情况下 (Task.Delay(1)
),异步操作基于计时器,不是线程阻塞在某处执行 Thread.Sleep
。大多数 I/O 都是以相同的方式完成的。例如,HttpClient.GetAsync
是基于重叠(异步)I/O,不是阻塞在某处等待 HTTP 下载完成的线程。
一旦理解了 await
如何使用它的上下文,遍历原始代码就更容易了:
static void Main(string[] args)
{
Console.WriteLine("Main: " + Thread.CurrentThread.ManagedThreadId);
MainAsync(args).Wait(); // Note: This is the same as "var task = MainAsync(args); task.Wait();"
Console.WriteLine("Main End: " + Thread.CurrentThread.ManagedThreadId);
Console.ReadKey();
}
static async Task MainAsync(string[] args)
{
Console.WriteLine("Main Async: " + Thread.CurrentThread.ManagedThreadId);
await thisIsAsync(); // Note: This is the same as "var task = thisIsAsync(); await task;"
}
private static async Task thisIsAsync()
{
Console.WriteLine("thisIsAsyncStart: " + Thread.CurrentThread.ManagedThreadId);
await Task.Delay(1); // Note: This is the same as "var task = Task.Delay(1); await task;"
Console.WriteLine("thisIsAsyncEnd: " + Thread.CurrentThread.ManagedThreadId);
}
- 主线程开始执行
Main
并调用MainAsync
。 - 主线程正在执行
MainAsync
并调用thisIsAsync
。 - 主线程正在执行
thisIsAsync
并调用Task.Delay
。 Task.Delay
做它的事情 - 启动计时器等等 - 并返回一个未完成的任务(注意Task.Delay(0)
将返回一个完成的任务,这会改变行为)。- 主线程返回到
thisIsAsync
并等待从Task.Delay
返回的任务。由于任务未完成,它从thisIsAsync
返回未完成的任务。 - 主线程返回到
MainAsync
并等待从thisIsAsync
返回的任务。由于任务未完成,它从MainAsync
返回未完成的任务。 - 主线程返回到
Main
并对从MainAsync
返回的任务调用Wait
。这将阻塞主线程,直到MainAsync
完成。 - 当
Task.Delay
设置的计时器关闭时,thisIsAsync
将继续执行。由于await
没有捕获到SynchronizationContext
或TaskScheduler
,它会继续在线程池线程上执行。 - 线程池线程到达
thisIsAsync
的末尾,完成其任务。 MainAsync
继续执行。由于await
没有捕获上下文,它会在线程池线程(实际上是运行thisIsAsync
的同一线程)上恢复执行。- 线程池线程到达
MainAsync
的末尾,完成其任务。 - 主线程从对
Wait
的调用中返回并继续执行Main
方法。用于继续thisIsAsync
和MainAsync
的线程池线程不再需要,返回线程池。
此处重要的一点是使用线程池因为没有上下文。它不会在“必要时”自动使用。如果您要在 GUI 应用程序中运行相同的 MainAsync
/thisIsAsync
代码,那么您会看到截然不同的线程用法:UI 线程有一个 SynchronizationContext
将延续安排回 UI 线程,因此所有方法都将在同一个 UI 线程上恢复。
关于c# - 异步/等待不同的线程 ID,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33821679/