0

I have some code which waits for a server response & uses a lambda to do stuff when it gets it. It also checks a class ivar, _timedOut, in this lambda, to see what to do. What I'm not sure of is, if _timedOut is changed somewhere else in the class after the lambda was created but before it's invoked, what value of _timedOut will the lambda see?

I've trawled SO for answers to this, but none of the answers seem to address this specific query. Code -

public class MyClass
{
    public MyClass()
    {
        _databaseService = //...database stuff
        _uploadService = //...uploads info
        _serverService = //...gets stuff from the server

        _uploadService.UploadingStatusChanged += UploadStatusChanged; 
    }

    private bool _timedOut = false;


    private void GetFinalInfo()
    {
        FinalInfo finalInfo = _databaseService.GetFinalInfo();

        if (finalInfo == null) // still have no finalInfo
        {

            _serverService.GetLatestFinalInfo((response, theFinalInfo) =>
            {
                if (!_timedOut) // this could be changed elsewhere in the class while we're waiting for the server response
                {
                    if (response == ServerResponse.Successful)
                    {
                        _databaseService.AddFinalInfo(theFinalInfo);
                        // navigate to next screen
                    }
                    else
                    {
                        // do something else
                    }
                }
            });
        }
        else
        {
            // navigate to next screen
        }
    }

}


private void UploadStatusChanged(object s, MyEventArgs e)
{
    // do stuff & call GetFinalInfo if good
}

Thanks for any help!

4

3 回答 3

3

_timeout将成为 lambda 闭包的一部分。

这意味着 lambda 中的值将是调用时的值。

于 2012-09-24T15:02:42.990 回答
2

我不确定的是,如果 _timedOut 在创建 lambda 之后但在它被调用之前在类中的其他地方发生了更改,那么 lambda 会看到什么值 _timedOut?

lambda 表达式将转换为实例方法,因为您this通过引用实例变量有效地捕获了引用(并且您没有捕获任何局部变量)。由 lambda 表达式创建的委托将有一个目标this,因此当执行委托时,它将“看到”对_timedOut.

当然,这仍然受到正常线程安全问题的影响——如果一个线程更改了变量的值,而没有任何额外的同步或内存屏障,则另一个线程可能会尝试读取该变量并查看旧值。

于 2012-09-24T15:08:34.507 回答
0

因为没有外部变量被认为需要捕获,_timedOut而不是被“捕获”。编译器所做的是在相关类上生成一个实例方法,并有效地将 lambda 中的代码“移动”到实例方法中,而不是创建一个闭包。例如,编译器将生成这样的方法MyClass

[CompilerGenerated]
private void <GetFinalInfo>b__0(ServerResponse response, object theFinalInfo)
{
    if (!this._timedOut)
    {
        if (response == ServerResponse.Successful)
        {
            this._databaseService.AddFinalInfo(theFinalInfo);
        }
    }
}

因此,lambda 中的代码将始终直接访问该_timedOut字段(以及该_databaseService字段)。如果您访问任何局部变量,则编译器将被迫通过生成包含它们的类来捕获该变量和任何其他“外部变量”,此时this将被捕获。例如,如果我们稍微修改一下代码:FinalInfo finalInfo = _databaseService.GetFinalInfo(); MyStruct myStruct = new MyStruct(); 我的结构.i = 1;

if (finalInfo == null) // still have no finalInfo
{

    _serverService.GetLatestFinalInfo((response, theFinalInfo) =>
                                        {
                                            Trace.WriteLine(myStruct.i);
                                        if (!_timedOut) // this could be changed elsewhere in the class while we're waiting for the server response
                                        {
                                            if (response == ServerResponse.Successful)
                                            {
                                                _databaseService.AddFinalInfo(theFinalInfo);
                                                // navigate to next screen
                                            }
                                            else
                                            {
                                                // do something else
                                            }
                                        }
                                        });
}
        }

编译器将生成代码以在 GetFinalInfo 中执行捕获:

MyClass.<>c__DisplayClass2 <>c__DisplayClass = new MyClass.<>c__DisplayClass2();
<>c__DisplayClass.<>4__this = this;
FinalInfo finalInfo = this._databaseService.GetFinalInfo();
<>c__DisplayClass.bleah = default(MyStruct);
<>c__DisplayClass.bleah.i = 1;
if (finalInfo == null)
{
    this._serverService.GetLatestFinalInfo(new Action<ServerResponse, object>(<>c__DisplayClass.<GetFinalInfo>b__0));
}

...清楚地制作this. 当然,即使在这种情况下,因为this只能是一个引用,所以当<>c__DisplayClass.<>4__this被引用时,它仍然是直接引用原始的_timedOut

现在,尽管直接访问该字段,编译器仍然可以自由地优化它对这个变量的使用,因为它不是通过 volatile 读取访问的。这与 lambda 的使用无关。如果不止一个线程在这里发挥作用,您可能会遇到代码可能看不到_timedOut在非 x86/x64 架构上进行的所有写入的情况。您似乎没有使用多个线程,并且您似乎没有以_timedOut一种方式使用会导致编译器生成不会_timedOut在 x86/x64 上的不同线程上看到更新的代码——但更改了代码可以介绍一下。

外部变量

范围包括 lambda 表达式或匿名方法表达式的任何局部变量、值参数或参数数组都称为匿名函数的外部变量。在类的实例函数成员中,this 值被视为值参数,并且是函数成员中包含的任何匿名函数的外部变量。

捕获

当一个外部变量被匿名函数引用时,该外部变量被称为已被匿名函数捕获。

于 2012-09-24T16:30:20.533 回答