这些可等待的方法之间有什么区别

本文关键字:之间 什么 区别 方法 等待 | 更新日期: 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,任务而不是 TResult - 只有一个例外:事件处理程序返回 void 而不是 Task。 - 每个调用异步函数的函数都应该声明为异步,否则使用 async-await 并不是很有用 - 调用异步方法后,您可以在煮鸡蛋时开始烤面包。当面包被烤好时,你可以等待鸡蛋,或者你可以在烤面包期间检查鸡蛋是否准备好了,或者最有效的方法是等待Task.WhenAny,继续完成鸡蛋或吐司或等待Task.WhenAll,当你没有任何有用的事情要做,只要没有两个都完成。

希望这个类比有帮助

相关文章: