.NET: How does the EventHandler race-condition fix work?

前端 未结 3 604
生来不讨喜
生来不讨喜 2021-02-08 04:24

There\'s the following pattern which is used to avoid a race condition when raising events in case another thread unsubscribes from MyEvent, making it null.

clas         


        
相关标签:
3条回答
  • 2021-02-08 05:02

    I would expect that once I got the MyEvent delegate inside the 'handler' reference, once somebody would change MyEvent that the object that 'handler' references will be changed as well. [..] Notice that System.Delegate is a class and not a struct.

    Although you are correct that delegate-types are references-types, they are immutable reference-types. From System.Delegate:

    "Delegates are immutable; once created, the invocation list of a delegate does not change.[...] Combining operations, such as Combine and Remove, do not alter existing delegates. Instead, such an operation returns a new delegate that contains the results of the operation, an unchanged delegate, or Nothing.


    On another note, the only issue this pattern addresses is preventing the attempted invocation of a null delegate-reference. Events are prone to races despite this "fix".

    0 讨论(0)
  • 2021-02-08 05:12

    Update

    Here are some diagrams that should hopefully clear up confusion about copying references and assignment.

    First: copying a reference.

    x = y

    In the above diagram, the reference contained in y is copied into x. No one's saying the object is copied; mind you—they point to the same object.

    Second: assigning a new reference to a variable.

    y += "!"

    Forget about the += operator for a moment; what I want to highlight above is that y is being assigned a different reference, to a new object. This does not affect x because x is its own variable. Remember, only the reference (the "address" in the diagram) had been copied to y.

    Third: same thing, only to x.

    x += "?"

    The above diagrams depict string objects, only because those are easy to represent graphically. But it's the same thing with delegates (and remember, standard events are just wrappers around delegate fields). You can see how by copying the reference in y into x above, we have created a variable which will not be affected by subsequent assignments to y.

    That is the whole idea behind the standard EventHandler race condition "fix" we're all familiar with.


    Original Answer

    You are probably confused by this tricky little syntax:

    someObject.SomeEvent += SomeEventHandler;
    

    What's important to realize is that, as Ani points out in his answer, delegates are immutable reference types (think: just like string). Many developers mistakenly think they are mutable because the above code looks like I am "adding" a handler to some mutable list. This isn't so; the += operator is an assignment operator: it takes the return value of the + operator and assigns it to the variable on the left side.

    (Think: int is immutable, and yet I can do int x = 0; x += 1; right? It's the same thing.)


    EDIT: OK, technically this is not quite right. Here is what really happens. An event is actually a wrapper around a delegate field that is accessible only (to external code) by the += and -= operators, which are compiled to calls to add and remove, respectively. In this way it is very much like a property, which is (typically) a wrapper around a field, where accessing the property and calling = are compiled to calls to get and set.

    But the point still remains: when you write +=, the add method that gets called is internally assigning a reference to a new object to the internal delegate field. I apologize for oversimplifying this explanation in my initial answer; but the key principle to understand is the same.

    By the way, I am not covering custom events where you can put your own logic inside the add and remove methods. This answer only applies to the "normal" case.


    In other words, when you do this...

    EventHandler handler = SomeEvent;
    if (handler != null)
    {
        handler(this, EventArgs.Empty);
    }
    

    ...you are indeed copying a reference into a variable. Now that reference is in the local variable and won't itself be modified. If it was pointing to an actual object at the time of assignment, then it will continue to point at that same (immutable) object on the next line. If it was not pointing to an object (null), then it will still not point to an object on the next line.

    So if code elsewhere subscribed or unsubscribed to the event using +=, what it really did was change the original reference to point to a completely new object. The old delegate object is still around, and you've got a reference to it: in your local variable.

    0 讨论(0)
  • 2021-02-08 05:22

    I would like to point out that comparing this incident to the 'int' case is probably inherently wrong since even though 'int' is atomic, it is a value type.

    But I think we've solved the case:

    Combining operations, such as Combine and Remove, do not alter existing delegates. Instead, such an operation returns a new delegate that contains the results of the operation, an unchanged delegate, or null. A combining operation returns null when the result of the operation is a delegate that does not reference at least one method. A combining operation returns an unchanged delegate when the requested operation has no effect.

    Delegate.CombineImpl Method shows the implementation.

    I looked over the implementation of Delegate and MulticastDelegate in the .NET 4 source code. Neither of them declare the += or -= operator. Coming to think of it, in Visual Basic.NET you don't even have them, you use AddHandler, etc...

    This means that the C# compiler implements this functionality and that the type doesn't really have anything to do with defining specialized operators.

    So this leads me to a logical conclusion which is, when you do:

    EventHandler handler = MyEvent;
    

    the C# compiler translates it to

    EventHandler handler = EventHandler.Combine(MyEvent)
    

    I'm surprised how quickly this question was solved thanks to your help.

    Thank you very much indeed!

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