繁体   English   中英

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

[英]What is the difference between these awaitable methods?

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

我是对的还是离开的?

这些方法实现都没有意义。 您所做的就是将阻塞工作推送到线程池(或者更糟糕的是,同步运行它并将结果包装在Task<Bar>实例中)。 你应该做的是暴露同步API,让调用者决定如何调用它。 他们是否想要使用Task.Run然后由他们决定。

话虽如此,这里有不同之处:

#1

第一个变体(返回通过Task.Run直接创建的Task<Bar> )是“最纯粹的”,即使它从API的角度来看没有多大意义。 您允许Task.Run在线程池上安排给定的工作,并将表示异步操作完成的Task<Bar>返回给调用者。

#2

第二种方法(利用Task.FromResult不是异步的。 它同步执行,就像常规方法调用一样。 结果只包含在已完成的Task<Bar>实例中。

#3

这是第一个更复杂的版本。 你正在实现类似于#1的结果,但是有额外的,不必要的,甚至有点危险的await 这个值得更详细地看一下。

async/await非常适合链接异步操作,通过将表示异步工作的多个Task组合到一个单元中( Task )。 它可以帮助您按正确的顺序进行操作,为您提供异步操作之间丰富的控制流,并确保在正确的线程上发生事情。

但是,上述情况都不会对您的方案有任何好处,因为您只有一个Task 因此,没有必要让编译器生成一个状态机,只是为了完成Task.Run已经完成的工作。

设计糟糕的async方法也很危险。 通过在await ed Task上不使用ConfigureAwait(false) ,您无意中引入了SynchronizationContext捕获,从而导致性能下降并引入死锁风险,从而无益。

如果你的调用者决定在具有SynchronizationContext (即Win Forms,WPF和可能的 ASP.NET)的环境中通过GetBar(fooId).Wait()GetBar(fooId).Result阻止你的Task<Bar> GetBar(fooId).Result ,他们将因为这里讨论的原因而陷入僵局。

我在Stackoverflow上的某个地方阅读了以下类比的评论。 因为它在评论中我找不到它,所以没有链接。

假设你必须做早餐。 你煮一些鸡蛋,烤面包。

如果你开始煮鸡蛋,然后在子程序“煮鸡蛋”的某个地方,你将不得不等到鸡蛋煮沸

同步将是你等到鸡蛋煮沸完毕后再启动子程序“吐司面包”。

然而,如果在煮鸡蛋时,你不要等待,但是你开始烘烤鸡蛋会更有效率。 然后你等待其中任何一个完成,并继续“煮鸡蛋”或“吐司面包”的过程,无论先完成。 这是异步但不并发。 仍然是一个人在做所有事情。

第三种方法是聘请一位煮鸡蛋的厨师,同时敬酒面包。 这实际上是并发的:两个人正在做某事。 如果你真的很有钱,你也可以租用烤面包机,而你看报纸,但是,嘿,我们并不都住在唐顿庄园;-)

回到你的问题。

Nr 2:是同步的:主线程完成所有工作。 鸡蛋煮沸后,此线程返回,然后调用者可以执行任何其他操作。

Nr 1未声明为异步。 这意味着虽然你启动了另一个可以完成工作的线程,但是你的来电者不能继续做其他事情,虽然你可以,但是你没有,你只需要等到鸡蛋煮沸。

第三个过程声明为异步。 这意味着,只要等待鸡蛋开始,你的来电者就可以做其他事情,比如敬酒面包。 请注意,这项工作所有工作都由一个线程完成。

如果你的调用者不等待你做任何事情而是等待你,除非你的调用者也被声明为异步,否则它将没有多大用处。 这将使呼叫者的呼叫者有机会做其他事情。

通常在正确使用async-await时,您会看到以下内容: - 声明为异步的每个函数都返回Task而不是void,而Task <TResult>而不是TResult - 只有一个例外:事件处理程序返回void而不是Task。 - 调用异步函数的每个函数都应该声明为异步,否则使用async-await并不是很有用 - 调用异步方法后,你可以在煮蛋时开始烘烤面包。 当面包烤好时,你可以等待鸡蛋,或者你可以在烘烤过程中检查鸡蛋是否准备好,或者最有效的是等待Task.WhenAny,继续完成鸡蛋或吐司或等待任务。当你没有任何有用的东西时,只要不是两个都完成了。

希望这个比喻有所帮助

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM