EventHandler: What is going on in this code?

2019-06-21 18:22发布

问题:

this code that adds registers new EventHandler(s) for an event named as NewMail (the eventargs class is named NewMailEventArgs.

// A PUBLIC add_xxx method (xxx is the event name)
// Allows methods to register interest in the event.
public void add_NewMail(EventHandler<NewMailEventArgs> value) {
  // The loop and the call to CompareExchange is all just a fancy way
  // of adding a delegate to the event in a thread-safe way.
  EventHandler<NewMailEventArgs> prevHandler;
  EventHandler<NewMailEventArgs> newMail = this.NewMail;
  do {
     prevHandler = newMail;
     EventHandler<NewMailEventArgs> newHandler = (EventHandler<NewMailEventArgs>)Delegate.Combine(prevHandler, value);
     newMail = Interlocked.CompareExchange<EventHandler<NewMailEventArgs>>(ref this.NewMail, newHandler, prevHandler);
    }
  while(newMail != prevHandler);
}

(source : CLR via C#, chapter 11 Events) What I don't understand is the do part, first we are assigning newMail to prevHandler, then newMail is changed (in CompareExchange) to newHandler? Then we are checking if newMail != prevHandler?
I am really kinda confused. Can any one help me understand what exactly is going on in here, specially in the do loop?

回答1:

As the comment says, it's to provide a kind of safety way to deal with events in multi-threaded environment. This is pretty tricky actually, but here's how it works:

  • What does Interlocked.CompareExchange is : if prevHandler == this.NewMail, then this.NewMail = newHandler

  • The whole operation (compare + affect) is atomic, i.e. done in one time (cannot be stopped in the middle of the operation by another thread).

  • If NewMail is not equal to prevHandler, then it means another thread already ran the same piece of code and already modified the event handler. So we won't do anything here and we'll loop and try again, hoping next time there is no other thread that already registered an event handler (next time we'll re-read the event handler; the operation done by the other thread will be now taken into account).

See also this useful thread.