如何调用保证同步的异步方法

时间:2020-03-04 11:11:54

标签: c# asp.net-core .net-core async-await

我有一个或多或少看起来像这样的方法:

public async Task PossiblyAsync(bool amIlucky)
{
    if (amIlucky)
        await Task.Delay(9000);

    return;
}

在某些情况下,我确定不会在其中调用任何异步代码路径,但是我仍然需要将所有异步操作做到最顶层。这本身不是问题,但是我想知道,当完美的同步代码伪装成异步的时,是否将所有结果都封装在Task<T>中,并进行其他操作,这是否会带来不必要的开销。

此外,我真的很希望它不会在线程之间跳转。我不确定是否会发生这种情况,特别是因为ASP Net Core中没有同步上下文。但是话又说回来,我什至不明白什么是同步上下文:)

调用这种方法的最正确方法是什么?

4 个答案:

答案 0 :(得分:3)

您可以重写方法以避免生成使方法异步的代码:

FlatList

您可以执行此操作,因为public Task PossiblyAsync(bool amIlucky) { if (amIlucky) return Task.Delay(9000); return Task.CompletedTask; } 完成后,您的方法无需执行任何操作。

Task.Delay始终处于完成状态,因此使用Task.CompletedTask调用您的方法时,将不会有任何内存分配。

答案 1 :(得分:3)

但是我想知道,当完美的同步代码假装为异步代码时,是否将所有不必要的结果包装在Task中并进行其他操作。

通常还不错。国家机械制造商会尝试针对这种情况进行优化,并避免一切不必要的事情-尽可能返回Task.CompletedTask。但是,对于返回Task<T>的方法,它不那么容易使用-您可能需要切换到ValueTask<T>,因为这在“经常同步但需要返回值”的情况下更为有效。在非常热的路径代码中(通常在IO库中),通常会在此处添加一个额外的层,以完全避免状态机,直到我们知道我们要进行异步处理为止,但这是利基,并且可能超出了范围您需要。


以“避免状态机”为例(并再次强调大多数应用代码将永远不需要这种优化级别)为例,请考虑:

ValueTask<Foo> SomeMethodAsync(...)
{
    ValueTask<Bar> theThing = DoTheThingAsync(...); // some other things that we need
    if (theThing.IsCompletedSuccessfully)
    {
        return new ValueTask<Foo>(ProcessResults(theThing.Result));
    }
    else
    {
        return Awaited(theThing);
    }

    static async ValueTask<Foo> Awaited(ValueTask<Bar> incomplete)
        => ProcessResults(await incomplete.ConfigureAwait(false));

    static Foo ProcessResults(Bar bar) {...whatever...}

答案 2 :(得分:1)

如果所有await都在等待已经完成的事情,那么这几乎不会带来任何开销。整个async/await功能已经进行了多年优化,以引入尽可能少的开销。特别是,已将async个状态机设置为struct,因此它们可以作为局部变量在堆栈上分配,并且仅在必须创建连续性时才升至堆。通过在可能的情况下使用class Task<>等来减轻创建struct ValueTask<>对象的开销。除非性能分析专门向您显示特定位置的大问题,否则您不必担心。

答案 3 :(得分:1)

我想知道,当完美的同步代码假装为异步代码时,是否会将所有结果包装在Task中并进行其他操作。

是; Task<T>已创建。当可能异步的代码同步运行时,这是唯一的开销。通常,您可以完全忽略该开销。如果此代码在循环中或类似情况下被紧密调用,那么您将希望避免那些多余的分配,您可以通过使用ValueTask<T>而不是Task<T>来实现。

此外,我真的希望它不会在线程之间跳转。

不会。根据定义,同步代码在继续执行同一线程之前,等待代码完成。对于可以可以异步但可以同步运行的代码也是如此。

调用这种方法的最正确方法是什么?

与其他任何异步方法一样,通过await设置其返回值。这样,此方法的调用者就不会对该方法的实现细节隐式依赖。因此,如果将来的重构使 all 代码路径异步,那么调用代码就可以工作而无需更改。