Clr via c # This book describes how events are implemented. CLR uses delegate fields to implement events:
// 1. a private delegate field that is initialized to null
Static EventHandler <EventArgs> NewMail = null;
// 2. a public add_Xxx method (where Xxx is the Event name)
// Allows methods to register interest in the event.
Static void add_NewMail (EventHandler <EventArgs> 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 <EventArgs> prevHandler;
EventHandler <EventArgs> tmp = NewMail;
Do
{
PrevHandler = tmp;
EventHandler <EventArgs> newHandler = (EventHandler <EventArgs>) Delegate. Combine (prevHandler, value );
Tmp = Interlocked. CompareExchange <EventHandler <EventArgs> (ref NewMail, newHandler, prevHandler );
} While (tmp! = PrevHandler );
}
// 3. a public remove_Xxx method (where Xxx is the Event name)
// Allows methods to unregister interest in the event.
Public void remove_NewMail (EventHandler <EventArgs> value)
{
// The loop and the call to CompareExchange is all just a fancy way
// Of removing a delegate from the event in a thread-safe way
EventHandler <EventArgs> prevHandler;
EventHandler <EventArgs> tmp = NewMail;
Do
{
PrevHandler = tmp;
EventHandler <EventArgs> newHandler = (EventHandler <EventArgs>) Delegate. Remove (prevHandler, value );
Tmp = Interlocked. CompareExchange <EventHandler <EventArgs> (ref NewMail, newHandler, prevHandler );
} While (tmp! = PrevHandler );
}
However, tmp = Interlocked. CompareExchange <EventHandler <EventArgs> (ref NewMail, newHandler, prevHandler );
This sentence is used to assign values. Why don't I use Interlocked. Exchange to directly assign values? Instead, use CompareExchange and (newMail! = PrevHandler.
In the case of a single thread, the two cases have the same effect.
In the case of multi-thread concurrency, we can find out why we can only use CompareExchange.
Assume that there are two threads A and B, and the Implementation response function is added at the same time, that is, the add_NewMail function is called.
Assume that the event NewMail initialization does not have A time response function, A needs to add A FunctionA response function, B needs to add A FunctionB function.
Step 1: thread A starts execution. After executing the following sentence, stop execution: EventHandler <EventArgs> newHandler = (EventHandler <EventArgs>) Delegate. combine (prevHandler, value );
NewMail is null, and newHandler is FunctionA. Thread A stops.
Step 2: thread B starts to execute and the entire function is executed,
NewMail is FunctionB.
Step 3: thread A continues to execute this statement: tmp = Interlocked. CompareExchange <EventHandler <EventArgs> (ref NewMail, newHandler, prevHandler );
Because NewMail = FunctionB at this time, tmp will = FunctionB,
But because prevHandler = FunctionA, NewMail! = PrevHandler, so the NewMail value remains unchanged.
Execute while (tmp! = PrevHandler), you will find tmp! = PrevHandler: the loop will continue until NewMail and prevHandler have the same value.
In this case, it is clear why Exchange cannot be used. If Exchange is used, the value assignment is executed in step 3, and NewMail is set to FunctionA, then FucntionB will not appear in the delegate linked list of the corresponding time function.
Why is CompareExchange okay?
In fact, it is the most fundamental principle to use CompareExchange to verify thread concurrency control: If an operation is to be viewed as non-disruptive, then from the beginning to the complete process, the operating environment cannot be changed.
When CompareExchange is used, we can verify that NewMail is the prevHandler in our initial operation. If not, it indicates that the basic environment of the operation has changed and this operation is invalid.
In this way, you can implement concurrency control when lock is not applicable, which is quite subtle.
From Dunnice