Is there a race condition in this common pattern used to prevent NullReferenceException?

前端 未结 2 664
我在风中等你
我在风中等你 2020-12-15 17:46

I asked this question and got this interesting (and a little disconcerting) answer.

Daniel states in his answer (unless I\'m readin

相关标签:
2条回答
  • 2020-12-15 18:01

    In CLR via C# (pp. 264–265), Jeffrey Richter discusses this specific problem, and acknowledges that it is possible for the local variable to be swapped out:

    [T]his code could be optimized by the compiler to remove the local […] variable entirely. If this happens, this version of the code is identical to the [version that references the event/callback directly twice], so a NullReferenceException is still possible.

    Richter suggests the use of Interlocked.CompareExchange<T> to definitively resolve this issue:

    public void DoCallback() 
    {
        Action local = Interlocked.CompareExchange(ref _Callback, null, null);
        if (local != null)
            local();
    }
    

    However, Richter acknowledges that Microsoft’s just-in-time (JIT) compiler does not optimize away the local variable; and, although this could, in theory, change, it almost certainly never will because it would cause too many applications to break as a result.

    This question has already been asked and answered at length in “Allowed C# Compiler optimization on local variables and refetching value from memory”. Make sure to read the answer by xanatox and the “Understand the Impact of Low-Lock Techniques in Multithreaded Apps” article it cites. Since you asked specifically about Mono, you should pay attention to referenced “[Mono-dev] Memory Model?” mailing list message:

    Right now we provide loose semantics close to ecma backed by the architecture you're running.

    0 讨论(0)
  • 2020-12-15 18:03

    This code will not throw a null reference exception. This one is thread safe:

    public void DoCallback() {
        Action local;
        local = Callback;
        if (local == null)
            local = new Action(() => { });
        local();
    }
    

    The reason this one is thread-safe, and can-not throw a NullReferenceException on Callback, is it's copying to a local variable before doing it's null check / call. Even if the original Callback was set to null after the null check, the local variable will still be valid.

    However the following is a different story:

    public void DoCallbackIfElse() {
        if (null != Callback) Callback();
        else new Action(() => { })();
    }
    

    In this one it's looking at a public variable, Callback can be changed to null AFTER the if (null != Callback) which would throw an exception on Callback();

    0 讨论(0)
提交回复
热议问题