Search code examples
c#.net-coreasync-awaittaskvaluetask

Is it necessary to check `ValueTask.IsCompleted` for performance?


  • case 1:
bool result = await DoAsync();
  • case 2:
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?


Solution

  • 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);
            }
        }