简体   繁体   English

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

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

I'm looking at some asynchronous programming in C# and was wondering what the difference would be between these functions that does the exact same thing and are all awaitable. 我正在研究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;
    });
}

My guess is that the first is the correct way to do things, and the code isn't executed until you try to get the result from the returned Task. 我的猜测是,第一种是正确的做事方式,并且在您尝试从返回的Task获取结果之前,代码不会执行。

In the second, the code is executed on call and the result is stored in the returned task. 在第二种情况下,代码在调用时执行,结果存储在返回的任务中。

And the third is kind of like the second? 第三种有点像第二种? The code is executed on call and the result of the Task.Run is return? 代码是在调用时执行的,Task.Run的结果是返回? Which in that case this function would be kind of stupid? 在这种情况下,这个函数会有点愚蠢吗?

Am I right or am I way off? 我是对的还是离开的?

None of these method implementations make sense. 这些方法实现都没有意义。 All you're doing is pushing out blocking work onto the thread pool (or worse, running it synchronously and wrapping the result in a Task<Bar> instance). 您所做的就是将阻塞工作推送到线程池(或者更糟糕的是,同步运行它并将结果包装在Task<Bar>实例中)。 What you should do instead is expose the synchronous API, and let the callers decide how to call it. 你应该做的是暴露同步API,让调用者决定如何调用它。 Whether they want to use Task.Run or not is then up to them. 他们是否想要使用Task.Run然后由他们决定。

Having said that, here are the differences: 话虽如此,这里有不同之处:

#1 #1

The first variant (which returns a Task<Bar> created via Task.Run directly) is the "purest" even if it doesn't make much sense from an API perspective. 第一个变体(返回通过Task.Run直接创建的Task<Bar> )是“最纯粹的”,即使它从API的角度来看没有多大意义。 You're allowing Task.Run to schedule the given work on the thread pool and return the Task<Bar> representing the completion of the async operation to the caller. 您允许Task.Run在线程池上安排给定的工作,并将表示异步操作完成的Task<Bar>返回给调用者。

#2 #2

The second method (which utilises Task.FromResult ) is not asynchronous. 第二种方法(利用Task.FromResult不是异步的。 It executes synchronously, just like a regular method call. 它同步执行,就像常规方法调用一样。 The result is simply wrapped in a completed Task<Bar> instance. 结果只包含在已完成的Task<Bar>实例中。

#3 #3

This is a more convoluted version of the first. 这是第一个更复杂的版本。 You are achieving an outcome similar to what #1 does, but with an extra, unnecessary, and even somewhat dangerous await . 你正在实现类似于#1的结果,但是有额外的,不必要的,甚至有点危险的await This one is worth looking at in more detail. 这个值得更详细地看一下。

async/await is great for chaining async operations via combining multiple Task s representing asynchronous work into a single unit ( Task ). async/await非常适合链接异步操作,通过将表示异步工作的多个Task组合到一个单元中( Task )。 It helps you get things happening in the right order, gives you rich control flow between your async operations and ensures that things happen on the right thread. 它可以帮助您按正确的顺序进行操作,为您提供异步操作之间丰富的控制流,并确保在正确的线程上发生事情。

None of the above, however, is of any benefit in your scenario, because you only have one Task . 但是,上述情况都不会对您的方案有任何好处,因为您只有一个Task Therefore, there is no need to make the compiler generate a state machine for you just to accomplish what Task.Run already does. 因此,没有必要让编译器生成一个状态机,只是为了完成Task.Run已经完成的工作。

A poorly designed async method can also be dangerous. 设计糟糕的async方法也很危险。 By not using ConfigureAwait(false) on your await ed Task you are inadvertently introducing a SynchronizationContext capture, killing performance and introducing a deadlock risk for no benefit. 通过在await ed Task上不使用ConfigureAwait(false) ,您无意中引入了SynchronizationContext捕获,从而导致性能下降并引入死锁风险,从而无益。

If your caller decides to block on your Task<Bar> in an environment which has a SynchronizationContext (ie Win Forms, WPF and possibly ASP.NET) via GetBar(fooId).Wait() or GetBar(fooId).Result , they will get a deadlock for the reasons discussed here . 如果你的调用者决定在具有SynchronizationContext (即Win Forms,WPF和可能的 ASP.NET)的环境中通过GetBar(fooId).Wait()GetBar(fooId).Result阻止你的Task<Bar> GetBar(fooId).Result ,他们将因为这里讨论的原因而陷入僵局。

I read somewhere on Stackoverflow in a comment the following analogy. 我在Stackoverflow上的某个地方阅读了以下类比的评论。 Because it is in a comment I can't find it easily, so no link to it. 因为它在评论中我找不到它,所以没有链接。

Suppose you have to make breakfast. 假设你必须做早餐。 You boil some eggs and toast some bread. 你煮一些鸡蛋,烤面包。

If you start boiling the eggs, then somewhere in the subroutine "Boil Egg" you'll have to wait until the eggs are boiled 如果你开始煮鸡蛋,然后在子程序“煮鸡蛋”的某个地方,你将不得不等到鸡蛋煮沸

Synchronous would be that you wait until the eggs are finished boiling before starting subroutine "Toast Bread". 同步将是你等到鸡蛋煮沸完毕后再启动子程序“吐司面包”。

However it would be more efficient if while the eggs are being boiled, you don't wait, but you start toasting eggs. 然而,如果在煮鸡蛋时,你不要等待,但是你开始烘烤鸡蛋会更有效率。 Then you wait for either of them to finish, and continue the process "Boil Eggs" or "Toast Bread" whichever finishes first. 然后你等待其中任何一个完成,并继续“煮鸡蛋”或“吐司面包”的过程,无论先完成。 This is asynchronously but not concurrent. 这是异步但不并发。 It is still one person who is doing everything. 仍然是一个人在做所有事情。

A third method would be to hire a cook who boils eggs while you toast the bread. 第三种方法是聘请一位煮鸡蛋的厨师,同时敬酒面包。 This is really concurrent: two persons are doing something. 这实际上是并发的:两个人正在做某事。 If you are really rich, you could also hire a toaster, while you read the newspaper, but hey, we don't all live in Downton Abbey ;-) 如果你真的很有钱,你也可以租用烤面包机,而你看报纸,但是,嘿,我们并不都住在唐顿庄园;-)

Back to your question. 回到你的问题。

Nr 2: Is synchronous: the main thread does all the work. Nr 2:是同步的:主线程完成所有工作。 This thread returns after the egg is boiled before the caller can do anything else. 鸡蛋煮沸后,此线程返回,然后调用者可以执行任何其他操作。

Nr 1 is not declared async. Nr 1未声明为异步。 This means that although you start another thread that will do the work, your caller can't continue doing something else, and although you could, you don't, you just wait until the egg is boiled. 这意味着虽然你启动了另一个可以完成工作的线程,但是你的来电者不能继续做其他事情,虽然你可以,但是你没有,你只需要等到鸡蛋煮沸。

The third procedure is declared async. 第三个过程声明为异步。 This means, that as soon as the waiting for the egg starts, your caller can do something else, like toasting bread. 这意味着,只要等待鸡蛋开始,你的来电者就可以做其他事情,比如敬酒面包。 Note that this the work all the work is done by one thread. 请注意,这项工作所有工作都由一个线程完成。

If your caller would wait not do anything but wait for you it would not be of very much use, unless your caller is also declared async. 如果你的调用者不等待你做任何事情而是等待你,除非你的调用者也被声明为异步,否则它将没有多大用处。 This would give the caller's caller the opportunity to do something else. 这将使呼叫者的呼叫者有机会做其他事情。

Generally when using async-await properly, you see the following: - Every function that is declared async returns Task instead of void, and Task < TResult > instead of TResult - There is only one exception: the event handler returns void instead of Task. 通常在正确使用async-await时,您会看到以下内容: - 声明为异步的每个函数都返回Task而不是void,而Task <TResult>而不是TResult - 只有一个例外:事件处理程序返回void而不是Task。 - Every function that calls an async function should be declared async, otherwise using async-await is not really useful - After calling an async method you can start toasting the bread while the egg is being boiled. - 调用异步函数的每个函数都应该声明为异步,否则使用async-await并不是很有用 - 调用异步方法后,你可以在煮蛋时开始烘烤面包。 When the bread is toasted you can await for the egg, or you can check if the egg is ready during the toasting, or maybe most efficient would be to await Task.WhenAny, to continue finishing either the egg or the toast or await Task.WhenAll when you don't have anything useful to do as long as not both are finished. 当面包烤好时,你可以等待鸡蛋,或者你可以在烘烤过程中检查鸡蛋是否准备好,或者最有效的是等待Task.WhenAny,继续完成鸡蛋或吐司或等待任务。当你没有任何有用的东西时,只要不是两个都完成了。

Hope this analogy helps 希望这个比喻有所帮助

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

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