Home > Enterprise >  C# await inside method arguments
C# await inside method arguments

Time:09-09

Is there any difference between this code:

MyFunction(await GetObject1(), await GetObject2());

and this:

var obj1 = await GetObject1();
var obj2 = await GetObject2();
MyFunction(obj1 , obj2);

As far as I know, arguments in C# are evaluated from left to right. But maybe in C# there are some optimizations that will start the calculation of both arguments and will wait for the first and then for the second?

CodePudding user response:

There is no difference.

When you use two await in sequence, they will be executed in sequence. This may be optimal or not, depending on the situation.

If you want to give "freedom" to the C# runtime to execute the async functions in any order or in parallel (this depends on the underlying implementation of each function), you can do:

var taskObj1 = GetObject1();
var taskObj2 = GetObject2();

await Task.WhenAll(taskObj1, taskObj2);

MyFunction(taskObj1.Result, taskObj2.Result);

That way, you are awaiting once for the two async functions, and can be optimal in some circumstances.

EDIT

Thanks @Ben for the comment. I added it together with the other tests.

Benchmark:

 var oneSecond = TimeSpan.FromSeconds(1);

 Stopwatch sw = new Stopwatch(); 
 
 sw.Start();
 await Task.Delay(oneSecond);
 await Task.Delay(oneSecond);
 sw.Stop();
 
 Console.WriteLine($"Using two awaits: {sw.ElapsedMilliseconds}");
 
 sw.Reset();
 
 sw.Start();
 await Task.WhenAll(Task.Delay(oneSecond), Task.Delay(oneSecond));
 sw.Stop();
 
 Console.WriteLine($"Using await Task.WhenAll: {sw.ElapsedMilliseconds}");
 
 sw.Reset();
 
 sw.Start();
 var task1 = Task.Delay(oneSecond);
 var task2 = Task.Delay(oneSecond);
 await task1;
 await task2;
 sw.Stop();
 
 Console.WriteLine($"Creating the tasks, then awaiting: {sw.ElapsedMilliseconds}");

Result:

enter image description here

CodePudding user response:

The short answer:

The difference is minimal. Code readability is probably your biggest gain, depending on your needs. There are tradeoffs with both approaches.

The long answer:

I dumped both variants into Sharplap.io to see what code compilation would look like.

Variant 1:

using System;
using System.Threading.Tasks;

public class C {
    public async void M() {
        MyFunction(await GetObject1(), await GetObject2());
    }

    public async Task<Object> GetObject1(){
        return new object();
    }

    public async Task<Object> GetObject2(){
        return new object();
    }

    public void MyFunction(Object object1, Object object2) {
    
    }
}

compiles to...

using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions;
using System.Threading.Tasks;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: UnverifiableCode]
public class C
{
    [StructLayout(LayoutKind.Auto)]
    [CompilerGenerated]
    private struct <M>d__0 : IAsyncStateMachine
    {
        public int <>1__state;

        public AsyncVoidMethodBuilder <>t__builder;

        public C <>4__this;

        private object <>7__wrap1;

        private TaskAwaiter<object> <>u__1;

        private void MoveNext()
        {
            int num = <>1__state;
            C c = <>4__this;
            try
            {
                TaskAwaiter<object> awaiter;
                if (num != 0)
                {
                    if (num == 1)
                    {
                        awaiter = <>u__1;
                        <>u__1 = default(TaskAwaiter<object>);
                        num = (<>1__state = -1);
                        goto IL_00ca;
                    }
                    awaiter = c.GetObject1().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(TaskAwaiter<object>);
                    num = (<>1__state = -1);
                }
                <>7__wrap1 = awaiter.GetResult();
                awaiter = c.GetObject2().GetAwaiter();
                if (!awaiter.IsCompleted)
                {
                    num = (<>1__state = 1);
                    <>u__1 = awaiter;
                    <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref this);
                    return;
                }
                goto IL_00ca;
                IL_00ca:
                object result = awaiter.GetResult();
                c.MyFunction(<>7__wrap1, result);
                <>7__wrap1 = null;
            }
            catch (Exception exception)
            {
                <>1__state = -2;
                <>t__builder.SetException(exception);
                return;
            }
            <>1__state = -2;
            <>t__builder.SetResult();
        }

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

    [StructLayout(LayoutKind.Auto)]
    [CompilerGenerated]
    private struct <GetObject1>d__1 : IAsyncStateMachine
    {
        public int <>1__state;

        public AsyncTaskMethodBuilder<object> <>t__builder;

        private void MoveNext()
        {
            object result;
            try
            {
                result = new object();
            }
            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);
        }
    }

    [StructLayout(LayoutKind.Auto)]
    [CompilerGenerated]
    private struct <GetObject2>d__2 : IAsyncStateMachine
    {
        public int <>1__state;

        public AsyncTaskMethodBuilder<object> <>t__builder;

        private void MoveNext()
        {
            object result;
            try
            {
                result = new object();
            }
            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);
        }
    }

    [AsyncStateMachine(typeof(<M>d__0))]
    public void M()
    {
        <M>d__0 stateMachine = default(<M>d__0);
        stateMachine.<>t__builder = AsyncVoidMethodBuilder.Create();
        stateMachine.<>4__this = this;
        stateMachine.<>1__state = -1;
        stateMachine.<>t__builder.Start(ref stateMachine);
    }

    [AsyncStateMachine(typeof(<GetObject1>d__1))]
    public Task<object> GetObject1()
    {
        <GetObject1>d__1 stateMachine = default(<GetObject1>d__1);
        stateMachine.<>t__builder = AsyncTaskMethodBuilder<object>.Create();
        stateMachine.<>1__state = -1;
        stateMachine.<>t__builder.Start(ref stateMachine);
        return stateMachine.<>t__builder.Task;
    }

    [AsyncStateMachine(typeof(<GetObject2>d__2))]
    public Task<object> GetObject2()
    {
        <GetObject2>d__2 stateMachine = default(<GetObject2>d__2);
        stateMachine.<>t__builder = AsyncTaskMethodBuilder<object>.Create();
        stateMachine.<>1__state = -1;
        stateMachine.<>t__builder.Start(ref stateMachine);
        return stateMachine.<>t__builder.Task;
    }

    public void MyFunction(object object1, object object2)
    {
    }
}

Variant 2:

using System;
using System.Threading.Tasks;

public class C {
    public async void M() {
        var obj1 = await GetObject1();
        var obj2 = await GetObject2();
        MyFunction(obj1, obj2);
    }

    public async Task<Object> GetObject1(){
        return new object();
    }

    public async Task<Object> GetObject2(){
        return new object();
    }

    public void MyFunction(Object object1, Object object2) {
    
    }
}

compiles to...

using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions;
using System.Threading.Tasks;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: UnverifiableCode]
public class C
{
    [StructLayout(LayoutKind.Auto)]
    [CompilerGenerated]
    private struct <M>d__0 : IAsyncStateMachine
    {
        public int <>1__state;

        public AsyncVoidMethodBuilder <>t__builder;

        public C <>4__this;

        private object <>7__wrap1;

        private TaskAwaiter<object> <>u__1;

        private void MoveNext()
        {
            int num = <>1__state;
            C c = <>4__this;
            try
            {
                TaskAwaiter<object> awaiter;
                if (num != 0)
                {
                    if (num == 1)
                    {
                        awaiter = <>u__1;
                        <>u__1 = default(TaskAwaiter<object>);
                        num = (<>1__state = -1);
                        goto IL_00ca;
                    }
                    awaiter = c.GetObject1().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(TaskAwaiter<object>);
                    num = (<>1__state = -1);
                }
                <>7__wrap1 = awaiter.GetResult();
                awaiter = c.GetObject2().GetAwaiter();
                if (!awaiter.IsCompleted)
                {
                    num = (<>1__state = 1);
                    <>u__1 = awaiter;
                    <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref this);
                    return;
                }
                goto IL_00ca;
                IL_00ca:
                object result = awaiter.GetResult();
                c.MyFunction(<>7__wrap1, result);
                <>7__wrap1 = null;
            }
            catch (Exception exception)
            {
                <>1__state = -2;
                <>t__builder.SetException(exception);
                return;
            }
            <>1__state = -2;
            <>t__builder.SetResult();
        }

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

    [StructLayout(LayoutKind.Auto)]
    [CompilerGenerated]
    private struct <GetObject1>d__1 : IAsyncStateMachine
    {
        public int <>1__state;

        public AsyncTaskMethodBuilder<object> <>t__builder;

        private void MoveNext()
        {
            object result;
            try
            {
                result = new object();
            }
            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);
        }
    }

    [StructLayout(LayoutKind.Auto)]
    [CompilerGenerated]
    private struct <GetObject2>d__2 : IAsyncStateMachine
    {
        public int <>1__state;

        public AsyncTaskMethodBuilder<object> <>t__builder;

        private void MoveNext()
        {
            object result;
            try
            {
                result = new object();
            }
            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);
        }
    }

    [AsyncStateMachine(typeof(<M>d__0))]
    public void M()
    {
        <M>d__0 stateMachine = default(<M>d__0);
        stateMachine.<>t__builder = AsyncVoidMethodBuilder.Create();
        stateMachine.<>4__this = this;
        stateMachine.<>1__state = -1;
        stateMachine.<>t__builder.Start(ref stateMachine);
    }

    [AsyncStateMachine(typeof(<GetObject1>d__1))]
    public Task<object> GetObject1()
    {
        <GetObject1>d__1 stateMachine = default(<GetObject1>d__1);
        stateMachine.<>t__builder = AsyncTaskMethodBuilder<object>.Create();
        stateMachine.<>1__state = -1;
        stateMachine.<>t__builder.Start(ref stateMachine);
        return stateMachine.<>t__builder.Task;
    }

    [AsyncStateMachine(typeof(<GetObject2>d__2))]
    public Task<object> GetObject2()
    {
        <GetObject2>d__2 stateMachine = default(<GetObject2>d__2);
        stateMachine.<>t__builder = AsyncTaskMethodBuilder<object>.Create();
        stateMachine.<>1__state = -1;
        stateMachine.<>t__builder.Start(ref stateMachine);
        return stateMachine.<>t__builder.Task;
    }

    public void MyFunction(object object1, object object2)
    {
    }
}

When diffing the results with Codebeautify.org/file-diff, I get about 8 lines of results that are very small changes. We get about 6 lines of changes between Variant 1 and Variant 2 that are basically variable name and label identifiers, and Variant 1 produces one more line of code than Variant 2 which nulls an object references within an exception block. Variant 1 has one object cast more than Variant 2 in the compiled result, but has one less null assignment.

Maybe over billions of iterations, you might see a small difference, but honestly, I think you're fine with either method.

Code readability, depending on context, is probably the biggest gain from this exercise.

  • Related