使用 async/await 会创建一个新线程吗?

     2023-03-07     190

关键词:

【中文标题】使用 async/await 会创建一个新线程吗?【英文标题】:Does the use of async/await create a new thread? 【发布时间】:2015-01-31 16:01:20 【问题描述】:

我是TPL 的新手,我想知道:C# 5.0 新增的异步编程支持(通过新的asyncawait 关键字)与线程的创建有何关系?

具体来说,async/await 的使用是否会在每次使用它们时创建一个新线程?如果有许多嵌套方法使用async/await,是否为每个方法创建一个新线程?

【问题讨论】:

取决于您使用await 调用的方法是如何实现的。您应该阅读blog.stephencleary.com/2013/11/there-is-no-thread.html 但请注意。 我们将深入研究。 否,例如见***.com/a/27071434/876814 相关/重复:async - stay on the current thread? 相关/重复:If async-await doesn't create any additional threads, then how does it make applications responsive? 【参考方案1】:

总之没有

来自Asynchronous Programming with Async and Await : Threads

async 和 await 关键字不会导致额外的线程 创建的。异步方法不需要多线程,因为异步 方法不在自己的线程上运行。该方法在当前运行 同步上下文并仅在线程上使用时间 方法处于活动状态。您可以使用 Task.Run 将 CPU 密集型工作移动到 后台线程,但后台线程对进程没有帮助 那只是在等待结果可用。

【讨论】:

这个问题我看了多少遍还是不明白。 “异步方法不需要多线程,因为异步方法不在自己的线程上运行” Ergo -> 另一个线程。不然怎么可能? 不是每个操作都需要一个线程。典型系统上有很多处理器/控制器,包括磁盘控制器、网卡控制器、GPU 等。它们只需要接收来自处理器的命令。然后他们继续执行命令并在完成时告诉处理器(通过中断或其他机制通知它)。在此之前,不涉及线程。发出线程可以进入睡眠状态,也可以进入可以重用的线程池。命令执行完毕后,程序的执行可能会被休眠线程或线程池线程继续执行。 如果 async/await 方法是一个 cpu 绑定方法 Task.Run 用于处理长时间运行的进程,在这种情况下 asycn 需要新线程,不是吗? @dudeNumber4 “一个异步方法不在它自己的线程上运行 -> 因此另一个线程”。不,它在同一个线程上运行!与调用它的方法相同的线程。它只是返回到该调用方法,以防它开始“等待”某些东西,以免浪费 CPU 周期。 @Bart CPU 周期与此 q/a 无关。【参考方案2】:

使用 Async/Await 不一定会创建新线程。但是使用 Async/Await 可能会导致创建一个新线程,因为可等待函数可能会在内部产生一个新线程。它经常这样做,使得“不,它不会产生线程”这句话在实践中几乎毫无用处。例如,以下代码生成新线程。

VisualProcessor.Ctor()

    ...
    BuildAsync();


async void BuildAsync()

    ...
    TextureArray dudeTextures = await TextureArray.FromFilesAsync(…);


public static async Task<TextureArray> FromFilesAsync(...)
    
    Debug.WriteLine("TextureArray.FromFilesAsync() T1 : Thread Id = " + GetCurrentThreadId());
    List<StorageFile> files = new List<StorageFile>();
    foreach (string path in paths)
    
        if (path != null)
            files.Add(await Package.Current.InstalledLocation.GetFileAsync(path)); // << new threads
        else
            files.Add(null);
    
    Debug.WriteLine("TextureArray.FromFilesAsync() T2 : Thread Id = " + GetCurrentThreadId());
    ...

【讨论】:

@GavinWilliams 用于回调的线程是由TaskScheduler.Current 在调用await 时选择的,而不是您调用 await 的函数。它返回时在不同的线程上运行的事实与GetFileAsync 无关 @GavinWilliams - 在您展示的代码中,没有任何内容可以使其成为一个很好的示例如果读者没有具体了解GetFileAsync 的实现。这使得这是一个糟糕的例子。 @Enigmativity 我不会让你感到抱歉。我认为通常 GetFileAsync 或任何其他方法的用户不会对它的实现有具体的了解。我将它作为 async / await 使用导致创建线程的示例。它确实显示了这个结果。这是一种非常常见的异步方法,我建议它是一个典型的例子。是否有一些不典型的东西使它成为一个糟糕的例子?实际上,如果您使用 async/await,则必须小心管理代码所在的线程。 @GavinWilliams - 如果我给了你一个方法的签名,比如说Task&lt;int&gt; GetAgeAsync(),你无法通过查看它来判断它是否会创建一个线程。你必须展示实现。所以你的例子是一样的。没有办法判断这是否创建了一个线程。如果你用GetFileAsyncGetAgeAsync 的完整实现来展示你自己的例子,那么你可以证明它有或没有。 无法判断——这就是我要说的。您不知道是否会创建线程,在我的回答中我说..“使用 Async/Await 不一定会导致创建新线程。但是使用 Async/Await 可能会导致新线程线程被创建,因为等待函数可能在内部产生一个新线程。”确实如此,尽管似乎还有另一种机制可以创建线程,正如 Scott Chamberlain 所建议的那样,即 TaskSchedular 可以自行决定是否生成线程。【参考方案3】:

所以我一直在阅读线程模型,并且 Async / Await 肯定会导致使用新线程(不一定创建 - 池在应用程序启动时创建它们)。由调度程序决定是否需要新线程。正如我所看到的,对可等待函数的调用可能具有内部细节,这些细节会增加调度程序利用另一个线程的机会;仅仅是因为更多的工作意味着调度员有更多的机会/理由来分配工作。

WinRT 异步操作自动发生在线程池上。通常你会从线程池中调用,除了 UI 线程工作.. Xaml/Input/Events。

在 Xaml/UI 线程上启动的异步操作会将其结果传递回 [调用] UI 线程。但是从线程池线程开始的异步操作结果会在完成发生的任何地方传递,这可能与您之前所在的线程不同。这背后的原因是,为线程池编写的代码很可能被编写为线程安全的,而且也是为了效率,Windows 不必协商线程切换。

同样,作为对 OP 的回答,不一定要创建新线程,但您的应用程序可以并且将使用多个线程来完成异步工作。

我知道这似乎与一些关于 async / await 的文献相矛盾,但那是因为虽然 async / await 构造本身并不是多线程的。 Awaitables 是调度程序可以分配工作和跨线程构造调用的机制之一。

这是我目前关于异步和线程的知识的极限,所以我可能并不完全正确,但我确实认为了解等待对象和线程之间的关系很重要。

【讨论】:

我认为这是错误的。你把纯await DoAsync()await Task.Run(Do) 混在一起。后者将因为 Task.Run 而使用线程池,但不是因为等待。我的意思是,甚至官方文档都说The async and await keywords don't cause additional threads to be created. @Blechdose 是的,据我了解,如果您只是在具有同步代码(单线程)的任务上使用 async / await 胶水,那很好,它将在调用时运行线。但是对于任何可等待的黑盒方法。你不知道是否会使用另一个线程。在等待之后,你永远不应该假设你在哪个线程上。【参考方案4】:

很抱歉迟到了。

我是 TPL 的新手,我想知道:异步如何 C# 5.0 新增的编程支持(通过新的 async 和 await 关键字)与线程的创建有关?

async/await不是为了创建线程而引入的,而是为了优化利用当前线程。

您的应用可能会读取文件、等待来自另一台服务器的响应,或者甚至进行具有高内存访问的计算(只需任何 IO 任务)。这些任务不是 CPU 密集型任务(任何不会使用 100% 线程的任务)。

想想当您处理 1000 个非 CPU 密集型任务时的情况。在这种情况下,创建 1000 个操作系统级线程的过程可能会比在单个线程上执行实际工作消耗更多的 CPU 和内存(Windows 中每个线程 4mb,4MB * 1000 = 4GB)。同时,如果您按顺序运行所有任务,则可能必须等到 IO 任务完成。这最终会在很长一段时间内完成任务,同时保持 CPU 空闲。

由于我们需要并行性来快速完成多个任务,同时所有并行任务都不会占用 CPU,但创建线程效率低。

编译器将在对async 方法(通过await 调用)的任何方法调用中中断执行,并立即在当前代码分支之外执行代码,一旦到达await,执行将进入之前的async。这将一次又一次地重复,直到所有异步调用都完成并且它们的awaiters 得到满足。

如果任何异步方法的 CPU 负载过重而没有调用异步方法,那么是的,您的系统将变得无响应,并且在当前任务完成之前不会调用所有剩余的异步方法。

【讨论】:

为什么说“编译器会在对异步方法的任何方法调用中中断执行(无论是否等待)”?微软表示:“如果 async 关键字修改的方法不包含 await 表达式或语句,则该方法同步执行”。在这种情况下, async 关键字根本不会中断执行流程。来源:docs.microsoft.com/fr-fr/dotnet/csharp/language-reference/… “(无论是否等待)”的措辞是错误的。现在修好了。谢谢指正。【参考方案5】:

根据 MSDN:async keyword

异步方法同步运行,直到它到达其第一个等待表达式,此时该方法被挂起,直到等待的任务完成。与此同时,控制权返回给方法的调用者,如下一节中的示例所示。

这是一个检查它的示例代码:

class Program


    static void Main(string[] args)
    
        Program p = new Program();
        p.Run();
    

    private void Print(string txt)
    
        string dateStr = DateTime.Now.ToString("HH:mm:ss.fff");
        Console.WriteLine($"dateStr Thread #Thread.CurrentThread.ManagedThreadId\ttxt");
    

    private void Run()
    
        Print("Program Start");
        Experiment().Wait();
        Print("Program End. Press any key to quit");
        Console.Read();
    

    private async Task Experiment()
    
        Print("Experiment code is synchronous before await");
        await Task.Delay(500);
        Print("Experiment code is asynchronous after first await");
    

结果:

我们看到在另一个线程上执行之后的 Experiment() 方法的代码。

但如果我用自己的代码替换 Task.Delay(方法 SomethingElse):

   class Program

    static void Main(string[] args)
    
        Program p = new Program();
        p.Run();
    

    private void Print(string txt)
    
        string dateStr = DateTime.Now.ToString("HH:mm:ss.fff");
        Console.WriteLine($"dateStr Thread #Thread.CurrentThread.ManagedThreadId\ttxt");
    

    private void Run()
    
        Print("Program Start");
        Experiment().Wait();
        Print("Program End. Press any key to quit");
        Console.Read();
    

    private async Task Experiment()
    
        Print("Experiment code is synchronous before await");
        await SomethingElse();
        Print("Experiment code is asynchronous after first await");
    

    private Task SomethingElse()
    
        Print("Experiment code is asynchronous after first await");
        Thread.Sleep(500);
        return (Task.CompletedTask);
    

我注意到线程保持不变!

最后,我会说 async/await 代码可以使用另一个线程,但前提是该线程是由另一个代码创建的,而不是由 async/await 创建的。

在这种情况下,我认为 Task.Delay 创建了线程,因此我可以得出结论 async/await 不会像 @Adriaan Stander 所说的那样创建新线程。

【讨论】:

谢谢。我认为它澄清了由于 Async Await 经常与 Task in TAP (Task Async Pattern) 一起使用而引起的一些混淆。因此,有些人错误地认为 Async Await 创建了一个新线程而不是 Task。据我了解,TAP 模式提供了一种更干净地管理多线程功能的方法。

异步编程async/await

...序代码不需要按照编写时的顺序严格执行,有时需要一在一个新的线程中运行一部分代码,有时无需创建新的线程,但是为了更好的利用单个线程的能力,需要改变代码的执行顺序。进程启动程序时,系统会在内存中创建一个新... 查看详情

Async/Await 是不是使用 Task.Run 异步启动新线程?

】Async/Await是不是使用Task.Run异步启动新线程?【英文标题】:IsAsync/AwaitusingTask.Runstartinganewthreadasynchronously?Async/Await是否使用Task.Run异步启动新线程?【发布时间】:2015-12-1923:41:47【问题描述】:我看了很多文章,仍然无法理解... 查看详情

async/await成对匹配,不是一个死循环吗

...it的作用是等待异步Task完成,并不是阻塞的。举个例子,一个异步方法:<pret="code"l="csharp">publicasyncTaskCaller()Action0();awaitMethod();Action3();publicasyncTaskMethod()Action1();awaitTask.Delay(1000);Action2();A.当你在非UI线程A上执行C... 查看详情

如何在 React (async/await) 中创建一个原子进程?

...点赞关联到特定帖子需要一点时间。现在,如果用户开始使用此代码快速按下按钮:state=isLiked:false,handlePress=()= 查看详情

Oracle 托管驱动程序可以正确使用 async/await 吗?

】Oracle托管驱动程序可以正确使用async/await吗?【英文标题】:CantheOraclemanageddriveruseasync/awaitproperly?【发布时间】:2015-05-1500:26:08【问题描述】:我试图使用async/await.NET功能进行Oracle查询。结果集非常大,大约需要5-10秒才能返回... 查看详情

输入语句块时会创建一个新的堆栈框架吗? [复制]

】输入语句块时会创建一个新的堆栈框架吗?[复制]【英文标题】:WillanewStackFramebecreatedonenteringablockofstatements?[duplicate]【发布时间】:2012-02-1404:09:06【问题描述】:可能重复:InC,dobracesactasastackframe?intmain()inti=10;inti=100;printf("%d",i)... 查看详情

[c#]开始接触async/await异步编程

...s异步?   启动程序时,系统会在内存中创建一个新的进程。进程是构成运行程序资源的集合。   在进程内部,有称为线程的内核对象 查看详情

几乎在任何地方都可以使用 async/await 吗?

】几乎在任何地方都可以使用async/await吗?【英文标题】:IsitOKtouseasync/awaitalmosteverywhere?【发布时间】:2016-05-2415:20:34【问题描述】:我目前正在编写供个人使用的小型NodeJSCLI工具,我决定尝试使用Babel的ES7async/await功能。这是一... 查看详情

async/await套路编程(代码片段)

...于并发任务,通常是用生成消费模型,对队列的处理可以使用类似master-worker的方式,master主要用户获取队列的msg,worker用户处理消息。为了简单起见,并且协程更适合单线程的方式,我们的主线程用来监听队列,子线程用于处... 查看详情

异步/等待与线程

...化异步编码。但是,我想知道可以Async/Await完全替代旧的使用方式Threads?Async/Await能做Thread能做的任何事情吗异步?Async/Await是否只能与WebClient.DownloadStrin 查看详情

c#中await和async的作用

...#39;s异步?   启动程序时,系统会在内存中创建一个新的进程。进程是构成运行程序资源的集合。   在进程内部,有称为线程的内核对象,它代表的是真正的执行程序。系统会在Main方法的第一行语句就开始线... 查看详情

如何使用 async/await 在 NodeJS 中创建 TCP 客户端?

】如何使用async/await在NodeJS中创建TCP客户端?【英文标题】:HowtocreateTCPclientinNodeJSusingasync/await?【发布时间】:2020-02-0523:17:32【问题描述】:我在nodejs中写了以下tcpclient。constnet=require(\'net\');constHOST=\'linux345\';constPORT=2345;letErrCode=1... 查看详情

操作系统:为什么io操作不占用cpu却会导致进程阻塞?web服务器每接收一个请求都会创建一个新的线程吗?tomcat服务器工作原理?

...用CPU却会导致进程阻塞?Web服务器每接收一个请求都会创建一个新的线程吗?这两个问题在我学操作系统以前我都挺困惑的。现在我来尝试着解答一下。1.为什么IO操作不占用CPU却会导致进程阻塞?IO操作是要等磁盘里... 查看详情

async/await的使用(代码片段)

async:使用async修饰符可将方法、lambda表达式或匿名方法指定为异步。如果对方法或表达式使用此修饰符,则其称为异步方法await:await运算符应用于异步方法中的任务,在方法的执行中插入挂起点,直到所等待的任务完成,返回线... 查看详情

我们需要在控制器中使用 async/await 关键字吗?

】我们需要在控制器中使用async/await关键字吗?【英文标题】:Areweneedtouseasync/awaitkeywordincontroller?【发布时间】:2021-09-1114:23:34【问题描述】:我有一个这样的用户控制器:publicclassUserController:ControllerBaseprivatereadonlyIUserService_userS... 查看详情

可以在构造函数中使用 async/await 吗?

】可以在构造函数中使用async/await吗?【英文标题】:Canasync/awaitbeusedinconstructors?【发布时间】:2016-07-2116:05:26【问题描述】:正如问题所述。我可以这样做吗:classMyClassasyncconstructor()returnnewPromise()【问题讨论】:async/await不是ES7... 查看详情

现在使用 async/await 安全吗? [关闭]

】现在使用async/await安全吗?[关闭]【英文标题】:Isitsafetouseasync/awaitnow?[closed]【发布时间】:2017-06-3004:54:58【问题描述】:在Javascript中使用async-await是否安全而不是generators-promises现在,知道语法尚未生​​成,并且会随着ES8的... 查看详情

一个高频问题:异步操作会创建线程吗?(代码片段)

这个问题在微信上被别人问过好多次,想来想去觉得有必要统一解答下,先说下我的答案:可能会,也有可能不会。要想寻找答案,需要从异步处理的底层框架说起。一:异步底层是什么异步从设计层面上来说它就是一个发布订... 查看详情