c# - 异步/等待不同的线程 ID

标签 c# asynchronous async-await

我最近在阅读有关 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发布以了解 asyncawait 关键字。特别是,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);
}
  1. 主线程开始执行 Main 并调用 MainAsync
  2. 主线程正在执行 MainAsync 并调用 thisIsAsync
  3. 主线程正在执行 thisIsAsync 并调用 Task.Delay
  4. Task.Delay 做它的事情 - 启动计时器等等 - 并返回一个未完成的任务(注意 Task.Delay(0) 将返回一个完成的任务,这会改变行为)。
  5. 主线程返回到 thisIsAsync 并等待从 Task.Delay 返回的任务。由于任务未完成,它从 thisIsAsync 返回未完成的任务。
  6. 主线程返回到 MainAsync 并等待从 thisIsAsync 返回的任务。由于任务未完成,它从 MainAsync 返回未完成的任务。
  7. 主线程返回到 Main 并对从 MainAsync 返回的任务调用 Wait。这将阻塞主线程,直到 MainAsync 完成。
  8. Task.Delay 设置的计时器关闭时,thisIsAsync 将继续执行。由于 await 没有捕获到 SynchronizationContextTaskScheduler,它会继续在线程池线程上执行。
  9. 线程池线程到达 thisIsAsync 的末尾,完成其任务。
  10. MainAsync 继续执行。由于 await 没有捕获上下文,它会在线程池线程(实际上是运行 thisIsAsync 的同一线程)上恢复执行。
  11. 线程池线程到达 MainAsync 的末尾,完成其任务。
  12. 主线程从对 Wait 的调用中返回并继续执行 Main 方法。用于继续thisIsAsyncMainAsync的线程池线程不再需要,返回线程池。

此处重要的一点是使用线程池因为没有上下文。它不会在“必要时”自动使用。如果您要在 GUI 应用程序中运行相同的 MainAsync/thisIsAsync 代码,那么您会看到截然不同的线程用法:UI 线程有一个 SynchronizationContext 将延续安排回 UI 线程,因此所有方法都将在同一个 UI 线程上恢复。

关于c# - 异步/等待不同的线程 ID,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33821679/

相关文章:

c# - JoinableTaskFactory.RunAsync 的正确用法是什么?

c# - 取消 token : why not to use AsyncLocal context instead of passing parameter to every async method?

c# - 我怎样才能等待 Task.WhenAll( ... ).ContinueWith( AnotherAwaitable )?

c# - 如何循环我的音频文件?

c# - 如何定义三个状态 bool 值但不是可空值?

java - Spring 任务异步和延迟

javascript - 我应该在 Promise.all 中使用 await 吗?

c# - FluentAssertions ShouldNotThrow 无法识别异步方法/Func

c# - C#运行时动态创建Label的宽度

c# - WebApi DI Autofac - 确保 Controller 具有无参数公共(public)构造函数