我有一个或多或少看起来像这样的方法:
public async Task PossiblyAsync(bool amIlucky)
{
if (amIlucky)
await Task.Delay(9000);
return;
}
在某些情况下,我确定不会在其中调用任何异步代码路径,但是我仍然需要将所有异步操作做到最顶层。这本身不是问题,但是我想知道,当完美的同步代码伪装成异步的时,是否将所有结果都封装在Task<T>
中,并进行其他操作,这是否会带来不必要的开销。
此外,我真的很希望它不会在线程之间跳转。我不确定是否会发生这种情况,特别是因为ASP Net Core中没有同步上下文。但是话又说回来,我什至不明白什么是同步上下文:)
调用这种方法的最正确方法是什么?
答案 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 代码路径异步,那么调用代码就可以工作而无需更改。