为啥对方法的第二个参数使用 await 运算符会影响第一个参数的值?

Posted

技术标签:

【中文标题】为啥对方法的第二个参数使用 await 运算符会影响第一个参数的值?【英文标题】:Why does using the await operator for the second argument to a method affect the value of the first argument?为什么对方法的第二个参数使用 await 运算符会影响第一个参数的值? 【发布时间】:2015-12-27 14:56:07 【问题描述】:

以下 C# 程序产生意外输出。我希望看到:

值1:25,值2:10

值1:10,值2:25

但我看到了

值1:0,值2:10

值1:10,值2: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 方法的第二个参数的表达式中使用“await”运算符似乎会影响第一个参数的值吗?

我的猜测是它一定与参数列表是从左到右进行评估的事实有关。在对PrintTwoValues 的第一次调用中,我猜测SomeClass.MyValue 的返回值会被推入堆栈。然后继续执行到GetValue,它只是启动任务并退出。然后DoWork 退出并安排一个将调用PrintTwoValues 的延续,但是当该延续运行时,最初压入堆栈的值不知何故丢失并恢复为默认值。

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

注意:我使用的是 Visual Studio 2013 Update 5。我正在构建一个面向 .NET Framework 4.5 并在 Windows 10 Enterprise 上运行的控制台应用程序。

【问题讨论】:

我得到了你期望得到的。 调试/发布模式下的行为是否改变? 有趣。我没有在发布模式下尝试过。看起来我确实在 Release 中得到了预期的结果,但在 Debug 中没有。 @StephenCleary 此处相同,但我可以在 VS2013 和 LinqPad 4 中重现。有趣的是,LinqPad 5 没有问题。我以为是 .Net Framework 版本,但是将 VS2015 降到 4.5 并没有出现问题。那么也许它与 C# 5 有关? @PauloMorgado,使用Task.Run 并没有什么不同。但是,感谢您指向该文章的指针。我不知道Task.Factory.StartNew 的危险。 【参考方案1】:

我已经分别使用 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 之前从未初始化。

【讨论】:

hmmm 会不会是编译器的代码优化问题? (取决于编译设置,他们可以做一些优化......或者至少尝试做这些)

以上是关于为啥对方法的第二个参数使用 await 运算符会影响第一个参数的值?的主要内容,如果未能解决你的问题,请参考以下文章

逗号运算符返回参数列表中的第一个值而不是第二个值?

async/await 面试题 加propmise

为啥这个的第二个版本在指数时间内运行?

为啥说重定向的第二个请求方法一定是get方法

为啥即使在 Spring 服务类的第二个方法中传播 = Propagation.REQUIRES_NEW 时事务也会回滚?

为啥我的第二个音频文件不能使用 currentTime 属性播放?