Sou*_*jji 2 c# asynchronous threadpool async-await
我已经发布了关于异步等待的各种文章,我试图理解等待异步的深度.我的问题是,我发现等待异步方法不会创建一个新线程,而只是让UI响应.如果它是这样的,那么在使用等待异步时没有时间增益,因为没有使用额外的线程.
到目前为止我所知道的是只有Task.Run()创建一个新线程.对于Task.WhenAll()或Task.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;
}
}
Run Code Online (Sandbox Code Playgroud)
我期待的是:
创建getStringTask任务时,另一个线程将复制当前上下文并开始执行GetStringAsync方法.
在等待getStringTask时,我们将看到其他线程是否已完成其任务,如果不是,则控件将返回AccessTheWebAsync()方法的调用者,直到另一个线程完成其恢复控制的任务为止.
所以我真的不知道在等待任务时如何创建额外的线程.有人可以解释在等待任务时究竟发生了什么吗?
Eri*_*ert 17
我已经发布了关于异步等待的各种文章,我试图理解等待异步的深度.
一种崇高的追求.
我的问题是,我发现等待异步方法不会创建新线程,而只是让UI响应.
正确.实现这await意味着异步等待是非常重要的.它并不意味着"使这个异步操作".它的意思是:
如果它是这样的,那么在使用等待异步时没有时间增益,因为没有使用额外的线程.
这是不正确的.你没有想到正确赢得时间.
想象一下这个场景.
假设有三个人排队,他们每个人都想要10美元.你加入了行的末尾,你只想要一美元.这是两个算法:
每个人都要等多久才能得到所有的钱?
那是一个同步算法.异步算法是:
这是一个异步解决方案.现在每个人都等了多久?
在为大型作业平均吞吐量较低,但是对于小型作业的平均吞吐量要高得多. 这就是胜利.此外,异步工作流程中每个人的首次购买时间都较低,即使大型工作的持续时间较长.此外,异步系统是公平的 ; 每个工作大约等于(工作的大小)x(工作的数量).在同步系统中,一些工作几乎没有时间等待,有些工作等待很长时间.
另一个胜利是:出纳员很贵; 该系统雇用单个出纳员,并为小型工作获得良好的吞吐量.为了在同步系统中获得良好的吞吐量,正如您所注意到的,您需要雇用更多昂贵的柜员.
对于Task.WhenAll()或Task.WhenAny(),这也是如此吗?
他们不创建线程.他们只需完成一系列任务,并在完成所有/任何任务后完成任务.
创建getStringTask任务时,另一个线程将复制当前上下文并开始执行GetStringAsync方法.
绝对不.该任务已经是异步的,因为它是一个IO任务,所以它不需要一个线程.IO硬件已经异步.没有招聘新员工.
等待getStringTask时,我们将看到其他线程是否已完成其任务
不,没有其他线程.我们看看IO硬件是否已完成其任务.没有线程.
当你把一块面包放在烤面包机里,然后检查你的电子邮件时,烤面包机里没有人在运行烤面包机.您可以启动异步作业然后在其工作时执行其他操作的事实是因为您具有本质异步的特殊用途硬件.网络硬件的确如此,就像烤面包机一样.没有线程.没有小人在运行烤面包机.它自己运行.
如果不是,控件将返回到AccessTheWebAsync()方法的调用者,直到另一个线程完成其任务以恢复控制.
同样,没有其他线程.
但控制流程是正确的.如果任务完成,则获取任务的值.如果未完成,则在将当前工作流的剩余部分指定为任务的延续之后,控制将返回到调用者.任务完成后,计划继续运行.
我真的不明白在等待任务时如何创建额外的线程.
再一次,想想你生命中的每一次,当你停止做任务因为你被阻止,做了一段时间的其他事情,然后在你被解锁时再次开始做第一个任务. 你是否需要雇佣一名工人? 当然不是.然而不知何故,当吐司在烤面包机中时,你设法制作鸡蛋.基于任务的异步只是将现实工作流程放入软件中.
它永远不会让我惊讶你今天的孩子你的奇怪的音乐行为像线程一直存在,没有其他方式做多任务处理.我学会了如何在一个操作系统,没有程序有线程.如果你想要同时发生两件事,你必须建立自己的异步; 它没有内置于语言或操作系统中.然而我们成功了.
合作单线程异步是一种回归世界,就像我们在将线程作为控制流结构引入错误之前一样; 一个更优雅,更简单的世界.等待是合作多任务系统中的暂停点.在预先线程化的Windows中,你需要Yield()它,我们没有语言支持来创建延续和闭包; 你希望状态坚持收益,你编写了代码来做到这一点.你们都很容易!
有人可以解释在等待任务时究竟发生了什么吗?
正是你所说的,只是没有线程.检查任务是否完成; 如果它完成了,你就完成了.如果没有,请将工作流的其余部分安排为任务的继续,然后返回.这一切都有await.
我只想确认一些事情.在等待任务时是否始终没有创建线程?
我们担心在设计一个人们会相信的功能时,就像你仍然可能的那样,"等待"会对它之后的呼叫产生影响.它没有.Await对返回值做了一些事情.再次,当你看到:
int foo = await FooAsync();
Run Code Online (Sandbox Code Playgroud)
你应该在精神上看到:
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;
Run Code Online (Sandbox Code Playgroud)
使用await调用方法不是一种特殊的调用. "等待"不会启动一个线程,或类似的东西.它是一个运算符,它根据返回的值进行操作.
所以等待任务不会启动一个线程.等待任务(1)检查任务是否完成,以及(2)如果不是,则将方法的其余部分指定为任务的继续,然后返回.就这样.Await没有做任何事情来创建一个线程.现在,也许被调用的方法会旋转一个线程; 这是它的业务.这与await无关,因为await直到调用返回后才会发生. 被调用的函数不知道正在等待它的返回值.
假设我们等待进行大量计算的CPU绑定任务.到目前为止我所知道的是一个I/O绑定代码,它将在低级CPU组件上执行(远低于线程),并且只是简单地使用一个线程来通知上下文关于完成的任务状态.
我们对上面调用FooAsync的了解是它是异步的,它返回一个任务.我们不知道它是如何异步的.这是FooAsync业务的作者!但是,FooAsync的作者可以使用三种主要技术来实现异步.如您所知,两种主要技术是:
如果任务是高延迟的,因为它需要在另一个CPU上的当前机器上进行长计算,那么获取工作线程并启动线程在另一个CPU上执行工作是有意义的.当工作完成时,相关任务可以安排其继续在UI线程上运行,如果任务是在UI线程上创建的,或者在适当时在另一个工作线程上创建.
如果任务是高延迟的,因为它需要与缓慢的硬件(如磁盘或网络)进行通信,那么正如您所注意到的那样,没有线程.专用硬件异步执行任务,操作系统提供的中断处理最终负责在正确的线程上安排任务完成.
异步的第三个原因不是因为你正在管理一个高延迟的操作,而是因为你将算法分成几个部分并将它们放在一个工作队列上.也许你正在制作自己的自定义调度程序,或者实现一个actor模型系统,或者尝试进行无堆栈编程,或者其他什么.没有线程,没有IO,但存在异步.
所以,再次,等待不会在工作线程上运行. 调用启动工作线程的方法可以在工作线程上运行.让你正在调用的方法决定是否创建一个工作线程. 异步方法已经是异步的.您无需对它们执行任何操作即可使它们异步.Await不会使任何异步.
Await的存在仅仅是为了让开发人员更容易检查异步操作是否已经完成,并且如果尚未完成,则将当前方法的剩余部分注册为继续.这就是它的用途.同样,await不会创建异步.Await 可帮助您构建异步工作流. 等待是工作流中的一个点,在工作流可以继续之前必须完成异步任务.
我也知道我们使用Task.Run()来执行CPU绑定代码以在线程池中查找可用线程.这是真的 ?
那是对的.如果您有一个同步方法,并且您知道它是CPU绑定的,并且您希望它是异步的,并且您知道该方法在另一个线程上运行是安全的,那么Task.Run将找到一个工作线程,时间表要在工作线程上执行的委托,并为您提供表示异步操作的任务.您应该只对以下方法执行此操作:(1)超长运行,超过30毫秒,(2)CPU绑定,(3)在另一个线程上安全调用.
如果你违反任何一个,就会发生坏事.如果你雇佣一名工人做不到30毫秒的工作,那么,想想现实生活.如果您有一些计算要做,那么购买广告,面试候选人,雇用某人,让他们一起添加三打数字然后解雇它们是否有意义? 雇用工人线程是昂贵的.如果雇佣线程比仅仅自己完成工作更昂贵,那么通过雇佣一个线程,你将无法获得任何绩效奖励; 你会让事情变得更糟.
如果你雇佣一名工人来完成IO约束任务,你所做的就是雇佣一名工作人员在邮箱旁坐多年,并在邮件到达时大喊大叫.这不会使邮件更快到达.它只会浪费可用于其他问题的工人资源.
如果你雇佣一名工人来完成一个非线程安全的任务,那么,如果你雇用两名工人并告诉他们同时将同一辆车开到两个不同的地方,那么他们就会让他们撞车.在高速公路上的方向盘上作战.