为什么对方法的第二个参数使用await操作符会影响第一个参数的值?

本文关键字:参数 影响 第一个 操作符 方法 第二个 为什么 await | 更新日期: 2023-09-27 18:15:04

下面的c#程序产生意想不到的输出。我希望看到:

Value1: 25, Value2: 10

Value1: 10, Value2: 25

但是我看到

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

谁能给我解释一下为什么使用"等待"操作符在表达式的第二个参数的PrintTwoValues方法似乎是影响第一个参数的值?

我猜这一定与实参列表是从左到右求值有关。在对PrintTwoValues的第一次调用中,我猜测SomeClass.MyValue的返回值被推到堆栈上。然后继续执行到GetValue,它刚刚启动任务并退出。然后DoWork退出并调度一个continuation,该continuation将调用PrintTwoValues,但是当该continuation运行时,堆栈上最初被压入的值不知怎么地丢失并恢复为默认值。

虽然有一些简单的方法可以解决这个问题,比如在将参数传递给PrintTwoValues方法之前将参数存储在临时变量中,但我主要只是好奇为什么会发生这种行为。

注意:我使用的是Visual Studio 2013, Update 5。我正在构建一个控制台应用程序,目标是。net Framework 4.5和运行在Windows 10企业。

为什么对方法的第二个参数使用await操作符会影响第一个参数的值?

我分别使用LinqPad 4和LinqPad 5在c# 5编译器和c# 6编译器上测试了代码,我可以重现这个问题。

这看起来像是c# 5编译器的编译错误,因为当我用。net Reflector 9反编译两个版本时,我得到了不同的代码:

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

请注意,对于c# 5, myValue变量在foo声明之前声明,并且在第一次调用PrintTwoValues之前不会初始化。