异步设计模式 - 哪种更好?

4

我开始思考异步编程时,是否应该始终使用以下模式:

public async Task<int> MyMethodAsync()
{
    return await SomeOtherMethodAsync();
}

或者说它是否安全且不会产生任何缺点,将此代码简化为:
public Task<int> MyMethodAsync()
{
    return SomeOtherMethodAsync();
}

我已经开始在互联网上阅读,但找不到任何答案,所有异步编程都只提到第一种模式。 更新1 根据一些答案,在简单的方法中,第二种方法是适当的。但如果我按以下方式扩展和编写方法 - 这仍然与上述代码相关,只是为了展示SomeOtherMethodAsync方法内部的内容:
public async Task<int> SomeOtherMethodAsync()
{
    var result1 = await LongRunningThirdMethod();
    var result2 = await LongRunningForthMethod();

    return result1 + result2;
}

并修改我的第二个示例为:

public Task<int> MyMethodAsync()
{
    SomeSyncMethod();
    SomeOtherSyncCode();

    return SomeOtherMethodAsync();
}

2
顺便提一下,SomeOtherMethodAsync 在开始 LongRunningFourthMethod 之前不必要等待 LongRunningThirdMethod - 实例化这两个任务,然后等待它们,以便它们可以并行运行。 - Ant P
谢谢,这只是一个简单的例子,内部是什么,可以扩展主要问题。 - Marcin
2个回答

2
在这种情况下,没有必要使用await开销。
异步/等待旨在使以前的回调地狱更像同步编程。由于您在异步调用之后没有任何事情要做,因此在这种情况下真的没有必要使用等待。
因此,您只需返回异步方法,甚至仍然保持其承诺,这一点是正确的。
更新1
这两种情况都是正确的。当您使用await/async时,编译器会在后台创建一个状态机来处理所有回调,如果您不使用它,则无需创建一个。因此,这两个示例都是适当的异步/等待代码。
但是,您可以通过同时等待两个任务来优化它。
public async Task<int> SomeOtherMethodAsync()
{
    var resultTask1 = LongRunningThirdMethod();
    var resultTask2 = LongRunningForthMethod();

    await Task.WhenAll(resultTask1, resultTask2);

    return resultTask1.Result + resultTask2.Result;
}

甚至可以是这样:
public async Task<int> SomeOtherMethodAsync()
{
    var resultTask1 = LongRunningThirdMethod();
    var resultTask2 = LongRunningForthMethod();

    return await resultTask1 + await resultTask2;
}

看看Yuval Itzchakov的答案,有另一种选择。关键是,在这里你同时开始两个任务,然后等待它们完成。而在你的例子中,你开始一个任务,等待它完成,然后开始下一个...等等。

值得注意的是,这仅适用于所讨论的琐碎情况 - 如果它应该代表一个一般性的例子,那么可能就不那么简单了。 - Ant P
@AntP 正确,我同意。但是如果我们看一个方法,只有一个对异步方法的调用,而且没有需要后续使用这个异步调用返回值的调用,那么你可以直接将“handle”返回给调用方,并让它处理。 - André Snede

1
这取决于你想要做什么。通常情况下,当你的方法只进行一次异步调用,且这是全部操作时,你可以省略状态机开销,直接返回 Task,在调用栈更高的位置等待其完成。
你的第二个示例(更新1)需要等待异步操作的两个结果返回。你可以修改代码,使用 Task.WhenAll 让两个操作同时运行:
public async Task<int> SomeOtherMethodAsync()
{
    int[] results = await Task.WhenAll(LongRunningThirdMethod(), LongRunningForthMethod());
    return results.Sum();
}

这样,您不需要按顺序等待每个操作完成。一旦它们都返回,您就可以计算总和。

1
并发示例非常微妙而珍贵。喜欢使用 Sum() - Gusdor
谢谢,我同意update1中的_SomeOtherMethodAsync_方法可以像你的示例一样写得更好。但实际上它只是扩展了该方法内部的内容。这是否改变了对pattern1和pattern2的理解和实现方式? - Marcin
1
在模式1中,由于您有两个异步操作需要完成,因为返回值取决于它们,所以无法避免使用await。在模式2中,您可以将Task向上传递,并让调用者使用await处理它。 - Yuval Itzchakov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接