Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does using the await operator for the second argument to a method affect the value of the first argument?

The following C# program produces unexpected output. I would expect to see:

Value1: 25, Value2: 10

Value1: 10, Value2: 25

but instead I see

Value1: 0, Value2: 10

Value1: 10, Value2: 25

namespace ConsoleApplication4
{
    class Program
    {
        static void Main(string[] args)
        {
            DoWork().Wait();

            Console.ReadLine();
        }

        private async static Task DoWork()
        {
            SomeClass foo = new SomeClass()
            {
                MyValue = 25.0f
            };

            PrintTwoValues(foo.MyValue, await GetValue());
            PrintTwoValues(await GetValue(), foo.MyValue);
        }

        static void PrintTwoValues(float value1, float value2)
        {
            Console.WriteLine("Value1: {0}, Value2: {1}", value1, value2);
        }

        static Task<float> GetValue()
        {
            return Task.Factory.StartNew(() =>
                {
                    return 10.0f;
                });
        }

        class SomeClass
        {
            private float myValue;

            public float MyValue
            {
                get
                {
                    return this.myValue;
                }
                set
                {
                    this.myValue = value;
                }
            }
        }
    }
}

Can somebody explain to me why it is that using the "await" operator in the expression for the second argument to the PrintTwoValues method seems to be affecting the value of the first argument?

My guess is that it must have something to do with the fact that the argument list is evaluated left-to-right. In the first call to PrintTwoValues I'm guessing that the return value from SomeClass.MyValue gets pushed onto the stack. Then execution continues into GetValue which just starts the Task and exits. Then DoWork exits and schedules a continuation that will call PrintTwoValues but when that continuation runs the value that had originally gotten pushed on the stack is somehow lost and reverted back to its default value.

While there are simple ways to workaround this problem, like storing the arguments in temporary variables before passing them to the PrintTwoValues method, I'm mostly just curious why this behavior is occurring.

Note: I'm using Visual Studio 2013, Update 5. I'm building a console application that is targeting .NET Framework 4.5 and running on Windows 10 Enterprise.

like image 378
supwar Avatar asked Sep 29 '15 23:09

supwar


People also ask

What does the await operator do?

The await operator suspends evaluation of the enclosing async method until the asynchronous operation represented by its operand completes. When the asynchronous operation completes, the await operator returns the result of the operation, if any.

What does the await keyword do JavaScript?

The await expression causes async function execution to pause until a Promise is settled (that is, fulfilled or rejected), and to resume execution of the async function after fulfillment. When resumed, the value of the await expression is that of the fulfilled Promise .

How does async await work?

The async keyword turns a method into an async method, which allows you to use the await keyword in its body. When the await keyword is applied, it suspends the calling method and yields control back to its caller until the awaited task is complete.

Does await block?

The await operator doesn't block the thread that evaluates the async method. When the await operator suspends the enclosing async method, the control returns to the caller of the method.


1 Answers

I've tested the code both with the C#5 compiler and the C#6 compiler using respectively LinqPad 4 and LinqPad 5 and I could reproduce the issue.

This looks like a compiler bug of the C#5 compiler, because when I decomplied both versions with .NET Reflector 9, I got different code:

C#5:

private async static Task DoWork()
{
    float myValue;
    SomeClass foo = new SomeClass {
        MyValue = 25f
    };
    float introduced6 = await GetValue();
    PrintTwoValues(myValue, introduced6);
    float introduced7 = await GetValue();
    PrintTwoValues(introduced7, foo.MyValue);
}

C#6:

private async static Task DoWork()
{
    SomeClass foo = new SomeClass {
        MyValue = 25f
    };
    float myValue = foo.MyValue;
    float num2 = await GetValue();
    float asyncVariable1 = num2;
    PrintTwoValues(myValue, asyncVariable1);
    num2 = await GetValue();
    float asyncVariable2 = num2;
    PrintTwoValues(asyncVariable2, foo.MyValue);
}

Notice that, for C#5, the myValue variable is declared before the declaration of of foo and never initialized before the first call to PrintTwoValues.

like image 101
Paulo Morgado Avatar answered Oct 22 '22 08:10

Paulo Morgado