bool result = await DoAsync();
ValueTask<bool> task = DoAsync();
bool result = task.IsCompleted ? task.Result : await task;
With case 1
and case 2
above,
can anyone say case 2
is better for performance(cpu, memory, etc.)?
Or, is task.IsCompleted
just duplicated and redundant?
To really know for sure, you'd need to measure using a suitable benchmark. However, I would not expect this to have any significant impact, since await
already does exactly that - albeit via GetAwaiter()
(which doesn't allocate). It could actually make the truly async scenario worse.
Something similar to this optimization is common in library code, but is typically used to avoid the state machine entirely when a result is likely to be synchronous a lot of the time; for example:
var pending = DoAsync(); // note: not awaited; this is a [Value]Task[<T>]
return pending.IsCompletedSuccessfully
? new ValueTask<Foo>(PostProcess(pending.Result))
: Awaited(pending);
static async ValueTask<Foo> Awaited(ValueTask<Bar> pending)
=> PostProcess(await pending.ConfigureAwait(false));
The key point here is that the original method is not async
in this scenario, so we only pay any async
overhead in the truly async path (or in the failure case, to standardise the error stack).
Here's a sharplab.io
link that shows what is going on with this optimization; on the right, you can see the optimized version that doesn't use async
, which comes out as:
public ValueTask<Foo> ExampleAsync()
{
ValueTask<Bar> pending = DoAsync();
if (!pending.IsCompletedSuccessfully)
{
return <ExampleAsync>g__Awaited|0_0(pending);
}
return new ValueTask<Foo>(PostProcess(pending.Result));
}
The Awaited
method, however is all of:
[AsyncStateMachine(typeof(<<ExampleAsync>g__Awaited|0_0>d))]
[CompilerGenerated]
internal static ValueTask<Foo> <ExampleAsync>g__Awaited|0_0(ValueTask<Bar> pending)
{
<<ExampleAsync>g__Awaited|0_0>d stateMachine = default(<<ExampleAsync>g__Awaited|0_0>d);
stateMachine.pending = pending;
stateMachine.<>t__builder = AsyncValueTaskMethodBuilder<Foo>.Create();
stateMachine.<>1__state = -1;
stateMachine.<>t__builder.Start(ref stateMachine);
return stateMachine.<>t__builder.Task;
}
[StructLayout(LayoutKind.Auto)]
[CompilerGenerated]
private struct <<ExampleAsync>g__Awaited|0_0>d : IAsyncStateMachine
{
public int <>1__state;
public AsyncValueTaskMethodBuilder<Foo> <>t__builder;
public ValueTask<Bar> pending;
private ConfiguredValueTaskAwaitable<Bar>.ConfiguredValueTaskAwaiter <>u__1;
private void MoveNext()
{
int num = <>1__state;
Foo result;
try
{
ConfiguredValueTaskAwaitable<Bar>.ConfiguredValueTaskAwaiter awaiter;
if (num != 0)
{
awaiter = pending.ConfigureAwait(false).GetAwaiter();
if (!awaiter.IsCompleted)
{
num = (<>1__state = 0);
<>u__1 = awaiter;
<>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref this);
return;
}
}
else
{
awaiter = <>u__1;
<>u__1 = default(ConfiguredValueTaskAwaitable<Bar>.ConfiguredValueTaskAwaiter);
num = (<>1__state = -1);
}
result = PostProcess(awaiter.GetResult());
}
catch (Exception exception)
{
<>1__state = -2;
<>t__builder.SetException(exception);
return;
}
<>1__state = -2;
<>t__builder.SetResult(result);
}
void IAsyncStateMachine.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
this.MoveNext();
}
[DebuggerHidden]
private void SetStateMachine(IAsyncStateMachine stateMachine)
{
<>t__builder.SetStateMachine(stateMachine);
}
void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
{
//ILSpy generated this explicit interface implementation from .override directive in SetStateMachine
this.SetStateMachine(stateMachine);
}
}