最近我读了一篇关于 Habré 的文章(更新:我从评论中意识到我需要附上一个引用,关于这个问题是进一步的)
一旦代码到达 Task.Run() 方法,就会从线程池中取出另一个线程,并在其中执行我们传递给 Task.Run() 的代码。旧线程,作为一个体面的线程,返回到池中并等待再次被调用以完成工作。新线程执行传递的代码,到达同步操作,同步执行(等待操作完成),然后继续执行代码。换句话说,操作一直保持同步:我们和以前一样,在同步操作执行期间使用线程。唯一的区别是我们在调用 Task.Run() 和返回 ExecuteOperation() 时花费了时间切换上下文。一切都变得更糟了。
那里讨论的问题之一是调用Task.Run
是一种反模式,它仅用于 GUI 响应性。
问题在于它在Task.Run(() => _anyWork())
哪里_anyWork()
包含同步代码。如果你这样做,文章中写的内容听起来很合乎逻辑:
await DoWork();
...
Task DoWork() => Task.Run(_work);
是的,在这种情况下,会在线程池上创建额外的负载。但是,如果你这样做:
var task1 = DoWork1();
var task2 = DoWork2();
var task3 = DoWork3();
await Task.WhenAll(task1, task2, task3);
Task.Run
立即变成普通代码,对吗?
将执行此代码的线程将创建三个其他线程(upd : speculative: 启动将工作添加到要运行的队列中ThreadPool
),它们将并行执行它们的工作。此外,当他遇到 时await
,他将返回控制权(最终,他很可能会返回池中)。如果不是请更正。
如果是这样,问题就来了:这条线在哪里,在一个糟糕的实现和一个正常的实现之间?在非库代码中,很明显 - 如果调用了一个方法,并且等待在更远的地方,那么您可以执行Task.Run
. 一方面,在库中,如果客户端在调用后等待它们,我们可以并行化方法的工作。另一方面,如果客户端在调用时期望立即得到结果,我们可以徒劳地增加负载。我们无法确切知道客户端将如何调用这些方法,我们只能在文档中给出建议。
也许有一些来自 MS 的官方建议?在 msdn 上,我只找到了对这些方法如何工作的干巴巴的描述。