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

在后台等待异步方法做什么?

微生嘉
2023-03-14

我已经阅读了许多关于异步wait的文章,我正在尝试深入了解wait异步。我的问题是,我发现等待异步方法不会创建一个新线程,它只会使UI响应。如果是这样,使用wait异步时没有时间增益,因为没有使用额外的线程。

到目前为止,我所知道的只是这项任务。Run()创建新线程。任务也是这样吗。WhenAll()或任务。WhenAny()?

假设我们有这样的代码:

    async Task<int> AccessTheWebAsync()
            {
                using (HttpClient client = new HttpClient())
                {
                    Task<string> getStringTask = client.GetStringAsync("https://docs.microsoft.com");

                    DoIndependentWork();

                    string urlContents = await getStringTask;

                    return urlContents.Length;
                }
            }

我的期望:

>

  • 创建getStringTask任务时,另一个线程将复制当前上下文并开始执行GetStringAsync方法。

    在等待getStringTask时,我们将查看另一个线程是否完成了他的任务,如果没有,控件将返回AccessTheWebAsync()方法的调用者,直到另一个线程完成它的任务以恢复控件。

    所以我真的不明白在等待任务时如何不创建额外的线程。有人能解释一下等待任务时到底发生了什么吗?

  • 共有3个答案

    严琨
    2023-03-14

    您的基本假设(任务总是在线程上运行)确实是错误的。一个简单的反例是一个根本不运行的基于计时器的任务:它只是订阅计时器,并在计时器触发时将任务状态设置为“已完成”。

    不在任何地方运行的任务的更有用和更实用的示例-网络请求:它们发送请求,订阅传入的答案,然后停止运行,释放线程以进行其他工作*。

    让我们考虑一下你的实际问题。

    到目前为止,我所知道的只是这项任务。Run()创建新线程。任务也是这样吗。WhenAll()或任务。WhenAny()?

    否,<代码>任务。whalll不会创建任何新线程。它将等待已经存在的任务完成,而不管它们在哪里运行(也不管它们是否在任何线程中运行!)。

    由任务创建的任务。当所有线程本身未在任何特定线程中运行时!它只检测底层任务何时完成,在所有任务都准备好之后,它也会自己完成<代码>任务。Whalll不需要任何线程来执行此操作。

    创建getStringTask任务时,另一个线程将复制当前上下文并开始执行GetStringAsync方法。

    正如我们前面看到的那样,调用异步方法(如GetStringAsync)不会在任何特定线程上执行。GetStringAsync的代码设置了这些内容,以便在得到答案时,它可以重新获得控制权(可能是在线程池线程上),并将控制权返回给您。准备工作可以在当前线程上完美完成,不需要太多时间*。

    *免责声明:这是一种简化,实际上,网络异步请求执行的操作序列要复杂得多。

    嵇光临
    2023-03-14

    如果是这样的话,那么在使用await-async时就没有时间了,因为没有使用额外的线程。

    这是正确的。异步和等待本身并不直接使用线程。它们的目的是释放调用线程。

    到目前为止,我所知道的只是这项任务。Run()创建新线程。任务也是这样吗。WhenAll()或任务。WhenAny()?

    不都不是任务。当所有任务都不执行时。任何时候都可以直接使用任何线程。

    创建getStringTask任务时,另一个线程将复制当前上下文并开始执行GetStringAsync方法。

    不会。与其他方法一样,在当前线程上同步调用GetStringAsync。它再次同步返回一个未完成的任务。

    在等待getStringTask时,我们将查看另一个线程是否完成了他的任务,如果没有,控件将返回AccessTheWebAsync()方法的调用者,直到另一个线程完成它的任务以恢复控件。

    关闭,但没有其他线程<代码>等待getStringTask将检查任务是否完成;如果不是,那么它将从访问WebAsync返回一个不完整的任务。

    有人能解释一下等待任务时到底发生了什么吗?

    我建议阅读我的异步简介以了解更多详细信息。

    谷梁子濯
    2023-03-14

    我红色的各种文章关于异步等待,我试图了解等待异步深入。

    Sublime的追求。

    我的问题是,我发现等待一个异步方法不会创建一个新线程,它只是使UI响应。

    正确。意识到wait意味着异步等待非常重要。它并不意味着“使此操作异步”。它的意思是:

    • 此操作已经是异步的
    • 如果操作完成,则获取其结果
    • 如果操作未完成,请返回调用方,并将此工作流的其余部分指定为未完成操作的继续
    • 当不完整操作完成时,它将安排继续执行

    如果是这样的话,那么在使用await-async时就没有时间了,因为没有使用额外的线程。

    这是不正确的。你没有正确考虑赢得时间。

    想象一下这种情况。

    • 想象一个没有自动取款机的世界。我在那个世界长大。那是一个奇怪的时代。所以银行通常会有一队人等着存钱或取钱。
    • 想象一下这家银行只有一个出纳员。
    • 现在想象一下,银行只收取和发放单一的美元钞票。

    假设有三个人在排队,他们每人想要十美元。你加入了队伍的末尾,你只想要一美元。这里有两种算法:

    • 给第一个排队的人一美元。
    • [那样做十次]
    • 给排队的第二个人一美元。
    • [那样做十次]
    • 给队伍中的第三个人一美元。
    • [那样做十次]
    • 给你你的美元。

    每个人要等多久才能拿到所有的钱?

    • 一个人等待10个时间单位

    这是一个同步算法。异步算法是:

    • 给第一个排队的人一美元。
    • 给排队的第二个人一美元。
    • 给队伍中的第三个人一美元。
    • 给你你的美元。
    • 给第一个排队的人一美元。
    • ...

    这是一个异步解决方案。现在大家要等多久?

    • 每个拿到10美元的人都要等30美元左右

    大型作业的平均吞吐量较低,但小型作业的平均吞吐量要高得多。这就是胜利。此外,在异步工作流中,每个人的第一美元时间都较低,即使大型作业的持续时间较高。此外,异步系统是公平的;每个作业大约等待(作业大小)x(作业数量)。在同步系统中,有些作业几乎不等待时间,有些作业等待很长时间。

    另一个胜利是:出纳员很贵;该系统只雇用一名出纳员,对于小型作业可以获得良好的吞吐量。正如您所指出的,为了在同步系统中获得良好的吞吐量,您需要雇佣更多的出纳员,这很昂贵。

    任务也是这样吗。WhenAll()或任务。WhenAny()?

    他们不创建线程。他们只是接受一堆任务,并在所有/任何任务完成后完成。

    创建getStringTask任务时,另一个线程将复制当前上下文并开始执行GetStringAsync方法。

    绝对不是。该任务已经是异步的,因为它是一个IO任务,所以不需要线程。IO硬件已经是异步的。没有雇用新工人。

    等待getStringTask时,我们将查看另一个线程是否已完成其任务

    不,没有其他线程。我们看看IO硬件是否完成了任务。没有线程。

    当你把一片面包放进烤面包机,然后去查看你的电子邮件时,烤面包机里没有人在运行烤面包机。你可以启动一项异步工作,然后在它工作的时候去做其他事情,这是因为你有特殊用途的硬件,本质上是异步的。网络硬件也是如此,就像烤面包机一样。没有线程。没有小人在运行你的烤面包机。它自己运行。

    如果不是,控件将返回AccessTheWebAsync()方法的调用者,直到另一个线程完成其任务以恢复控件。

    同样,没有其他线程。

    但是控制流是正确的。如果任务完成,则获取任务的值。如果它不完成,则在将当前工作流的剩余部分分配为任务的延续之后,控制返回给调用者。当任务完成时,计划运行延续。

    我真的不明白在等待任务时如何没有创建额外的线程。

    再一次,想想你生命中的每一次,当你因为被阻止而停止做一项任务,做了一段时间其他的事情,然后当你被阻止时又开始做第一项任务。你必须雇一个工人吗?当然不是。然而,不知怎的,当吐司在烤面包机里的时候,你还是设法做了鸡蛋。基于任务的异步只是将真实世界的工作流放入软件中。

    我一直很惊讶,今天你们这些带着奇怪音乐的孩子表现得就像线程一直存在,没有其他方法可以处理多任务。我学会了如何在没有线程的操作系统中编程。如果你想同时发生两件事,你必须建立自己的异步;它没有内置在语言或操作系统中。然而我们成功了。

    协作单线程异步是对世界的回归,就像我们错误地将线程作为控制流结构引入之前一样;一个更加优雅、更加简单的世界。等待是协作多任务系统中的暂停点。在预线程窗口中,您会为此调用Yield(),而我们没有创建continuations和closure的语言支持;您希望状态在整个产量中保持不变,您编写了代码来做到这一点。你们都很容易!

    有人能解释一下等待任务时到底发生了什么吗?

    正如你所说,只是没有线索。检查任务是否完成;如果完成了,你就完成了。如果没有,请将工作流的其余部分安排为任务的继续,然后返回。这就是等待所做的一切。

    我只是想确认一些事情。等待任务时总是没有线程创建的情况吗?

    我们在设计功能时担心人们会相信(你仍然可能会相信)“wait”会对它之后的调用产生影响。它没有。等待会对返回值产生影响。同样,当您看到:

    int foo = await FooAsync();
    

    你应该在精神上看到:

    Task<int> task = FooAsync();
    if (task is not already completed) 
       set continuation of task to go to "resume" on completion
       return;
    resume: // If we get here, task is completed
    int foo = task.Result;
    

    对带有await的方法的调用不是一种特殊的调用。“等待”并不是一条线索,或者类似的东西。它是对返回的值进行操作的运算符。

    因此,等待任务不会启动线程。等待任务(1)检查任务是否完成,如果未完成,则将方法的剩余部分指定为任务的继续部分,并返回。这就是全部。Wait不执行任何创建线程的操作。现在,可能被调用的方法旋转了一个线程;这就是生意。这与等待无关,因为等待直到调用返回后才会发生。被调用的函数不知道正在等待其返回值。

    假设我们正在等待一个CPU受限的任务,该任务需要进行大量计算。到目前为止,我所知道的是一个i/O绑定代码,它将在低级别CPU组件(远低于线程)上执行,并且只会短暂地使用线程来通知上下文有关已完成任务的状态。

    关于上面对FooAsync的调用,我们知道它是异步的,并且它返回一个任务。我们不知道它是如何异步的。这是FooAsync业务的作者!但FooAsync的作者可以使用三种主要技术来实现异步。正如您所注意到的,两种主要技术是:

    >

    如果任务的延迟很高,因为它需要与速度较慢的硬件(如磁盘或网络)进行通信,那么正如您所注意到的,就没有线程。专用硬件异步执行任务,操作系统提供的中断处理最终负责在正确的线程上完成任务。

    保持异步的第三个原因不是因为你正在管理一个高延迟的操作,而是因为你正在将算法分解成小部分并将它们放在工作队列中。也许你正在制作自己的自定义调度程序,或者实现一个参与者模型系统,或者尝试进行无堆栈编程,或者其他什么。没有线程,没有IO,但有异步。

    同样,等待不会让某些东西在工作线程上运行。调用启动工作线程的方法会让某些东西在工作线程上运行。让您正在调用的方法决定是否创建工作线程。异步方法已经是异步的。您不需要对它们做任何事情就可以使它们异步。等待不会使任何东西异步。

    wait的存在只是为了让开发人员更容易检查异步操作是否已完成,如果当前方法的剩余部分尚未完成,则将其注册为continuation。这就是为什么。同样,wait不会创建异步。Wait可帮助您构建异步工作流。等待是工作流中的一个点,在该点上,必须先完成异步任务,然后工作流才能继续。

    我也知道我们使用任务。Run()执行CPU绑定代码,以在线程池中查找可用线程。这是真的吗?

    没错。如果您有一个同步方法,并且您知道它是CPU受限的,并且您希望它是异步的,并且您知道该方法可以安全地在另一个线程上运行,那么请执行任务。Run将找到一个工作线程,安排在工作线程上执行委托,并为您提供一个表示异步操作的任务。您应该只对(1)运行时间非常长的方法执行此操作,例如,超过30毫秒,(2)CPU受限,(3)可以安全地调用另一个线程。

    如果你违反了其中的任何一条,坏事就会发生。如果你雇佣一个工人做不到30毫秒的工作,那么,想想现实生活。如果你有一些计算要做,那么购买广告、面试候选人、雇佣某人,让他们把36个数字加在一起,然后解雇他们有意义吗?雇佣一个工人线程是昂贵的。如果雇佣线程比自己做工作更昂贵,那么雇佣线程根本不会获得任何绩效优势;你会让事情变得更糟。

    如果你雇了一个工人来完成IO绑定的任务,你所做的就是雇了一个工人坐在邮箱旁多年,在邮件到达时大喊大叫。这并不能使邮件更快到达。这只会浪费原本可以用于其他问题的员工资源。

    如果你雇佣一个工人来做一项不安全的任务,如果你雇佣两个工人,告诉他们同时把同一辆车开到两个不同的地方,他们会在高速公路上争夺方向盘时撞车。

     类似资料:
    • 在实际应用中,我对 C# 中的异步和 await 方法进行了说明。请考虑以程,这是工作代码的简单版本: 助手.cs 功能控制器.cs processing.js 现在,问题是第三方服务的所需时间更少(例如:2秒),而所需的时间更多(例如:100秒)。进行两个ajax调用并使用异步和等待是为了呈现自我。屏幕上显示数据1,不要等到自己。Data2已被检索。不幸的是,这种情况没有发生,我必须等待100秒

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

    • 存储库: 数据:

    • 我正在编写一个WinForms应用程序,它将数据传输到USB HID类设备。我的应用程序使用了优秀的通用HID库V6.0,可以在这里找到。简单来说,当我需要向设备写入数据时,这是被调用的代码: 当我的代码退出while循环时,我需要从设备中读取一些数据。但是,设备无法立即响应,因此我需要等待此呼叫返回后再继续。由于当前存在,RequestToGetInputReport()声明如下: GetInp

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

    • 我通读了Dart/flatter中的Async/Await/then,试图理解为什么aysnc函数中的Await不会等到完成后再继续。在我的UI中,有一个按钮调用一个异步方法来返回一个位置,该位置总是返回null,并且不等待函数完成。 该函数将调用推送到一个新的UI页面,该页面选择一个位置,并应返回一个结果。如何使该函数等待结果?我不是在使用异步吗?