当前位置: 首页 > 知识库问答 >
问题:

异步/等待不同的线程ID

童浩言
2023-03-14

我最近阅读了有关async/await的文章,我感到困惑的是,我阅读的许多文章/帖子都指出,在使用async await(示例)时不会创建新线程。

我创建了一个简单的控制台应用程序来测试它

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
Rather, it enables the method to be split into multiple pieces, some of which may run asynchronously

我想知道,如果没有创建其他线程,异步部分在哪里运行?如果它运行在同一个线程上,难道它不应该因为长的I/O请求而阻塞它吗?或者编译器足够聪明,如果它需要太长的时间,就可以将该操作移到另一个线程上,并且毕竟使用了一个新线程?

共有1个答案

屈俊远
2023-03-14

我建议您阅读我的Async介绍文章,了解Asyncawait关键字。特别是,await(默认情况下)将捕获一个“上下文”,并使用该上下文恢复其异步方法。此“上下文”是当前synchronizationcontext(如果没有synchronzationcontext,则为TaskScheduler)。

我想知道如果没有创建其他线程,异步部分在哪里运行?如果它运行在同一个线程上,难道它不应该因为长的I/O请求而阻塞它吗?或者编译器足够聪明,如果它需要太长的时间,就可以将该操作移到另一个线程上,并且毕竟使用了一个新线程?

正如我在博客上解释的那样,真正的异步操作不会在任何地方“运行”。在这种特殊情况下(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. 返回一个不完整的任务
  7. 主线程返回到MainAsync并等待从ThisisAsync返回的任务。由于任务不完整,它将从MainAsync.
  8. 返回一个不完整的任务
  9. 主线程返回main并对从mainAsync返回的任务调用wait。这将阻塞主线程,直到MainAsync完成。
  10. task.delay设置的计时器关闭时,Thisisasync将继续执行。由于没有synchronizationcontexttaskscheduler被该await捕获,因此它将继续在线程池线程上执行。
  11. 线程池线程到达ThisisAsync的末尾,完成任务。
  12. MainAsync继续执行。因为Await没有捕获上下文,所以它继续在线程池线程上执行(实际上是运行ThisisAsync)的同一线程。
  13. 线程池线程到达MainAsync的末尾,完成任务。
  14. 主线程从对wait的调用中返回,并继续执行main方法。用于继续ThisisAsyncMainAsync的线程池线程不再需要,并返回线程池。

这里重要的一点是,使用线程池是因为没有上下文。它不是“在必要时”自动使用的。如果要在GUI应用程序中运行相同的MainAsync/ThisisAsync代码,那么您将看到非常不同的线程用法:UI线程有一个SynchronizationContext,它将继续调度回UI线程,因此所有方法都将在同一个UI线程上恢复。

 类似资料:
  • 我正在尝试将数据库调用移出控制器,以清理并使其可测试。当它们在控制器中时,一切都会顺利进行。我将它们移出控制器,并添加了一个异步,以确保我们等待。否则,我将调用的中的函数。现在,一旦我使用async/await,控制器中的函数就会认为没有用户,因为它没有等待。 有几个关于异步等待的SO问题,但我没有找到一个解决我的问题。我确实验证了返回了我的用户,并添加了控制台日志来显示路径。 节点猫鼬异步等待似

  • 我知道使用无线程异步有更多线程可用于服务输入(例如HTTP请求),但我不明白当异步操作完成并且需要一个线程来运行它们的延续时,这如何不可能导致线程饥饿。 假设我们只有3个线程 并且它们在需要线程的长时间运行的操作中被阻塞(例如在单独的db服务器上进行数据库查询) 使用async Wait,您可以 然而,在我看来,这可能会导致“正在进行”的异步操作过剩,如果太多的操作同时完成,那么就没有线程可以运行

  • 问题内容: 我目前正在等待所有承诺按顺序完成,如下所示: 但是,通过这种方式,配置文件和令牌将顺序执行。由于两者彼此独立,因此我希望两者一起独立执行。我认为可以使用Promise.all完成此操作,但是我不确定语法,也找不到任何帮助。 所以我的问题是如何转换上面的api调用以一起运行,然后返回最终输出。 问题答案:

  • 我试图在react/electron项目中使用async/await,但它不起作用。我想要的是获取docker容器状态列表。但是安慰。日志(列表)返回未定义的。 有人能帮我吗?:)

  • 我正试图将图像上传到firebase存储,但调用该函数时,未执行wait以获取url。我错过了什么? 看看这个其他主题,我发现问题可能是“然后”,但我如何设置代码以等待url? 异步/等待/然后飞镖/颤振 谢谢

  • 我正在为一个大型应用程序编写自动化测试。这些测试中的一些很容易成为<code>异步,它只提供<code>async 测试应用程序的一些关键方面如下: 这是一个巨大的ASP.NET应用程序(尽管代码在通过单元测试执行时没有在ASP.NET上下文中运行)。 在其核心中,它严重依赖于每个线程缓存上下文信息(例如活动用户的整个权限方案)的静态对象。 现在,我的问题是当在方法中使用时,延续可能发生在与以前不