这些等待方法有什么区别?

时间:2016-02-22 10:05:50

标签: c# asynchronous async-await task

我正在研究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的结果是返回?在这种情况下,这个函数会有点愚蠢吗?

我是对的还是离开的?

2 个答案:

答案 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,继续完成鸡蛋或吐司或等待任务。当你没有任何有用的东西时,只要不是两个都完成了。

希望这个比喻有帮助