我正在研究C#中的一些异步编程,并想知道这些函数之间的差异是什么,它们完全相同并且都是等待的。
public Task<Bar> GetBar(string fooId)
{
return Task.Run(() =>
{
var fooService = new FooService();
var bar = fooService.GetBar(fooId);
return bar;
});
}
public Task<Bar> GetBar(string fooId)
{
var fooService = new FooService();
var bar = fooService.GetBar(fooId);
return Task.FromResult(bar)
}
public async Task<Bar> GetBar(string fooId)
{
return await Task.Run(() =>
{
var fooService = new FooService();
var bar = fooService.GetBar(fooId);
return bar;
});
}
我的猜测是第一种是正确的做事方式,直到你试图从返回的Task获得结果时才会执行代码。
在第二个中,代码在调用时执行,结果存储在返回的任务中。
第三种有点像第二种?代码是在调用时执行的,Task.Run的结果是返回?在这种情况下,这个函数会有点愚蠢吗?
我是对的还是离开的?
答案 0 :(得分:6)
这些方法实现都没有意义。您所做的就是将阻塞工作推送到线程池(或者更糟糕的是,同步运行它并将结果包装在Task<Bar>
实例中)。你应该做的是暴露同步API,让调用者决定如何调用它。他们是否想要使用Task.Run
然后取决于他们。
话虽如此,这里有不同之处:
<强>#1 强>
第一个变体(直接返回通过Task<Bar>
创建的Task.Run
)是“最纯粹的”,即使它从API的角度来看没有多大意义。您允许Task.Run
在线程池上安排给定的工作,并将表示异步操作完成的Task<Bar>
返回给调用者。
<强>#2 强>
第二种方法(使用Task.FromResult
)不异步。它同步执行,就像常规方法调用一样。结果只包含在已完成的Task<Bar>
实例中。
<强>#3 强>
这是第一个更复杂的版本。你正在实现类似于#1的结果,但是有额外的,不必要的,甚至有些危险await
。这个值得更详细地看一下。
async/await
组合到一个单元(Task
)中, Task
非常适合链接异步操作。它可以帮助您按正确的顺序完成任务,为您提供异步操作之间丰富的控制流,并确保在正确的线程上发生事情。
但是,上述情况都不会对您的方案有任何好处,因为只有一个Task
。因此,没有必要让编译器为您生成状态机,只是为了完成Task.Run
已经完成的任务。
设计不良的async
方法也可能很危险。如果您在ConfigureAwait(false)
await
上未使用Task
,则会无意中引入SynchronizationContext
捕获,从而导致性能下降并导致死锁风险,无法获益。
如果您的来电者决定通过{{1}在Task<Bar>
的环境中阻止您SynchronizationContext
(即Win表单,WPF和可能 ASP.NET) }}或GetBar(fooId).Wait()
,由于here所讨论的原因,它们会陷入僵局。
答案 1 :(得分:0)
我在Stackoverflow上的某个地方阅读了以下类比的评论。因为它在评论中我无法轻易找到它,所以没有链接。
假设你必须做早餐。你煮一些鸡蛋,烤面包。
如果你开始煮鸡蛋,那么在子程序的某个地方&#34; Boil Egg&#34;你必须要等到煮鸡蛋
同步将是你等到鸡蛋煮完之后再开始子程序&#34; Toast Bread&#34;。
然而,如果在煮鸡蛋时,你不会等待,但是你开始烘烤鸡蛋会更有效率。然后你等待其中任何一个完成,并继续这个过程&#34; Boil Eggs&#34;或者&#34;吐司面包&#34;无论哪个先完成。 这是异步但不并发。仍然是一个人在做所有事情。
第三种方法是聘请一位煮鸡蛋的厨师,同时吐面包。这实际上是并发的:两个人正在做某事。如果你真的很有钱,你也可以租一台烤面包机,而你看报纸,但是,嘿,我们都不住在唐顿庄园; - )
回到你的问题。
Nr 2:是同步的:主线程完成所有工作。鸡蛋煮沸后,该线程返回,然后调用者可以执行任何其他操作。
Nr 1未声明为异步。这意味着虽然你开始另一个可以完成工作的线程,但是你的来电者不能继续做其他的事情,尽管你可以,但是你不要等到蛋被煮沸。
第三个过程被声明为异步。这意味着,只要等待鸡蛋开始,你的来电者就可以做其他事情,比如敬酒面包。请注意,这项工作所有工作都由一个线程完成。
如果你的来电者等不做任何事情而不是等你,那就不会有太多用处,除非你的来电者也被宣布为异步。这将使呼叫者的呼叫者有机会做其他事情。
通常在正确使用async-await时,您会看到以下内容: - 声明为异步的每个函数都返回Task而不是void,而Task&lt; TResult&gt;而不是TResult - 只有一个例外:事件处理程序返回void而不是Task。 - 每个调用异步函数的函数都应声明为异步,否则使用async-await并不是很有用 - 在调用异步方法后,你可以在鸡蛋煮沸时开始烘烤面包。当面包烤好时,你可以等待鸡蛋,或者你可以在烘烤过程中检查鸡蛋是否准备好,或者最有效的是等待Task.WhenAny,继续完成鸡蛋或吐司或等待任务。当你没有任何有用的东西时,只要不是两个都完成了。
希望这个比喻有帮助