supwar
supwar

Reputation: 222

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.

Upvotes: 8

Views: 147

Answers (1)

Paulo Morgado
Paulo Morgado

Reputation: 14856

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.

Upvotes: 5

Related Questions