这些可等待的方法之间有什么区别
本文关键字:之间 什么 区别 方法 等待 | 更新日期: 2023-09-27 18:35:36
我正在研究 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.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
Task
上使用ConfigureAwait(false)
,您将无意中引入SynchronizationContext
捕获,从而降低性能并引入死锁风险,而没有任何好处。
如果您的调用方决定通过GetBar(fooId).Wait()
或GetBar(fooId).Result
在具有SynchronizationContext
(即 Win Forms、WPF 和可能的 ASP.NET)的环境中阻止您的Task<Bar>
,他们将由于此处讨论的原因而陷入死锁。
我在Stackoverflow的评论中读到以下类比。因为它在评论中,所以我不容易找到它,所以没有指向它的链接。
假设你必须做早餐。你煮一些鸡蛋,烤一些面包。
如果你开始煮鸡蛋,那么在子程序"煮鸡蛋"中的某个地方,你必须等到鸡蛋煮熟
同步是你等到鸡蛋煮完再开始子程序"吐司面包"。
但是,如果在煮鸡蛋时,您不等待,而是开始烤鸡蛋,那会更有效率。然后你等待它们中的任何一个完成,并继续"煮鸡蛋"或"吐司面包"的过程,以先完成者为准。这是异步的,但不是并发的。仍然是一个人在做所有事情。
第三种方法是聘请一个厨师,他在你烤面包时煮鸡蛋。这实际上是同时进行的:两个人在做某事。如果你真的很有钱,你也可以在看报纸的时候雇一个烤面包机,但是嘿,我们并不都住在唐顿庄园;-)
回到你的问题。
Nr 2:同步:主线程完成所有工作。此线程在鸡蛋煮沸后返回,然后调用方才能执行任何其他操作。
Nr 1 未声明为异步。这意味着,尽管您启动了另一个线程来完成这项工作,但您的调用方无法继续执行其他操作,尽管您可以,但您没有,您只需等到鸡蛋煮沸。
第三个过程被声明为异步。这意味着,一旦等待鸡蛋开始,您的呼叫者就可以做其他事情,例如烤面包。请注意,这项工作的所有工作都由一个线程完成。
如果您的调用方等待不做任何事情,而是等待您,那么除非调用方也被声明为异步,否则它不会有多大用处。这将使调用方的调用方有机会执行其他操作。
通常,正确使用 async-await 时,您会看到以下内容: - 每个声明为异步的函数都返回任务而不是 void,任务
希望这个类比有帮助