Click here to Skip to main content
Email Password   helpLost your password?

Disclaimer: This blog entry only deals with the common case of instance events; static events are ignored. Furthermore, the contents of this blog entry are 100% my own opinion. However, it is the opinion of someone who has specialized in multithreading for 13 years.

When writing components in a multithreaded world, one question that commonly crops up is, "how do I make my events threadsafe?" The asker is usually concerned with threadsafe subscription and unsubscription, but threadsafe raising must also be taken into consideration.

The Wrong Solution #1, from the C# Language Specification

The C# language authors attempted to make event subscription and unsubscription threadsafe by default. To do so, they allow (but do not require) locking on this, which is generally considered bad. This code:

public event MyEventHandler MyEvent;

Logically becomes this code:

private MyEventHandler __MyEvent;
public event MyEventHandler MyEvent
{
    add
    {
        lock (this)
        {
            this.__MyEvent += value;
        }
    }

    remove
    {
        lock (this)
        {
            this.__MyEvent -= value;
        }
    }
}

Chris Burrows, a Microsoft developer on the C# compiler team, explains why this is bad in his blog post Field-like Events Considered Harmful. His blog post covers the reasoning thoroughly, so it won't be repeated here.

Minor rant: The Java language fell into the same trap; see Practical API Design's Java Monitor page. Why is it that some language designers believe they can declaratively solve multithreading problems? If the solution was really as simple as that, then why haven't other people already figured it out? Multithreaded programming has consumed some of the brightest minds for decades, and it's hard. Language designers can't make multithreading complexities go away by sprinkling some magical fairy powder, even if they name the powder "MethodImplOptions.Synchronized". In fact, most of the time they're just making it worse.

Pretend for a minute that locking on this is OK. It actually would work, after all; it just raises the likelihood of unexpected deadlocks. It's also possible that a future C# compiler may lock on a super-secret private field instead of this. However, even if the implementation is OK, the design is still flawed. The problem becomes clear when one ponders how to raise the event in a threadsafe manner.

This is the standard, simple, and logical event-raising code:

if (this.MyEvent != null)
{
    this.MyEvent(this, args);
}

If there are multiple threads subscribing to and unsubscribing from an event, then the built-in field-like event locking works only for subscribing and unsubscribing. The event raising code exposes a problem: if another thread unsubscribes from the event after the if statement but before the event is raised, then this code may result in a NullReferenceException!

So, it turns out that "thread-safe" events weren't really thread-safe. Moving on...

The Wrong Solution #2, from the Framework Design Guidelines and MSDN

One solution to the problem described above is to make a copy of the event delegate before testing it. The event raising code becomes:

MyEventHandler myEvent = this.MyEvent;
if (myEvent != null)
{
    myEvent(this, args);
}

This is the solution used by MSDN examples and recommended by the semi-standard Framework Design Guidelines (my 2nd edition has it on page 157, but the relevant section of the book is available online here).

This solution is simple, obvious, and wrong. [By the way, I'm not dissing Framework Design Guidelines. They have lots of good advice, and I don't mean to be critical of the book in general. They're just mistaken in this particular recommendataion.]

Programmers without a strong background in multithreaded programming may not immediately detect why this solution is wrong. Delegates are immutable reference types, so the local variable copy is atomic; no problem there. The problem exists in the memory model: it is possible that an out-of-date value for the delegate field is held in one processor's cache. Without going into a painful level of detail, in order to ensure that one is reading the current value of a non-volatile field, one must either issue a memory barrier or wrap the copy operation within a lock (and it must be the same lock acquired by the event add/remove methods).

In short, this solution does prevent the NullReferenceException race condition; but it introduces another race condition in its place (raising an unsubscribed event handler).

The Wrong Solution #3, from Jon Skeet

[OK, let me say this first: Jon Skeet is an awesome programmer. I highly recommend his book C# in Depth to anyone and everyone using C# (I own the first edition and will buy the 2nd as soon as it comes out; he's writing it now and I'm so excited!). I follow his blog. I highly respect the man, and I can't believe my first mention of him on my blog is in a negative light... However, he did come up with a wrong solution for thread-safe events. To give him credit, though, he ended his paper recommending the right solution!]

Jon Skeet has a great treatment of this subject in his paper Delegates and Events (you may wish to skip to the section titled "Thread-safe events"). He covers everything that I've described above, but then proceeds on to propose another wrong solution. He dislikes the memory barrier solution (as do I), and attempts to solve it by wrapping the copy operation within the lock. As Jon points out, the event add/remove methods may lock this or they could lock something else (remember, a future C# compiler may choose to lock on a super-secret private field instead). So, the default add/remove methods have to be replaced with ones that perform an explicit lock, as such:

private object myEventLock = new object();
private MyEventHandler myEvent;
public MyEventHandler MyEvent
{
    add
    {
        lock (this.myEventLock)
        {
            this.myEvent += value;
        }
    }

    remove
    {
        lock (this.myEventLock)
        {
            this.myEvent -= value;
        }
    }
}

protected virtual OnMyEvent(MyEventArgs args)
{
    MyEventHandler localMyEvent;
    lock (this.myEventLock)
    {
        localMyEvent = this.myEvent;
    }

    if (localMyEvent != null)
    {
        localMyEvent(this, args);
    }
}

That's a fair amount of code for a single event! Some people have even written helper objects to reduce the amount of code. Before jumping on that bandwagon, though, remember that this solution is also wrong.

There is still a race condition.

Specifically, it is possible that the value of myEvent is modified after it has been read into localMyEvent but before it is raised. This can result in an unsubscribed handler being invoked, which could be problematic. So, this solution does solve the last solution's problem (with the memory model and processor cache), but it turns out there was an underlying race condition anyway (this same problem does affect the other two solutions above, too).

The Wrong Solution #4, from Nobody (but just in case you were thinking about it!)

A natural response is to extend the lock statement in Jon's code to include raising the event. That does prevent the race condition problem from all the solutions described above, but it introduces a more serious problem.

If this solution is used, then an event handler cannot wait on another thread that is attempting to subscribe or unsubscribe a handler to the same event. In other words, it's the original "unexpected deadlock" story (the same reason why locking on this is bad). Jon does make a note of this in Delegates and Events.

To my knowledge, no one has proposed this as a solution. In general, the community seems to favor solutions that fail "loudly" (with exceptions) instead of failing "silently" (with a deadlock).

Why All Solutions are Wrong, by Stephen Cleary (that's me!)

"Callbacks" (usually events in C#) have always been problematic for multithreaded programming. This is because a good rule of thumb for component design is: Do your best to allow the event handler to do anything. Since "communicate with another thread that is attempting to take any lock" is one example of "anything", a natural corollary of this rule is: Never hold locks during callbacks.

This is the reasoning behind why locking on exposed objects (such as this) is considered bad practice (see MSDN: lock Statement). Holding that lock while raising an event (such as solution 4 does) makes the bad practice even worse.

To review, all the solutions above fail in one of two situations.

Solutions 1-3 above all fail the same use case:

Solution 4 fails this use case:

A general-purpose "thread-safe event" solution does not exist - at least, not using the synchronization primitives we currently have at our disposal. The implementation must either have a race condition or deadlock possibility. A lock can prevent contention (solving the race condition), but only if it is held during the raising of the event (possibly causing deadlock). Alternatively, an unadorned raised event does not have the possibility of a deadlock, but loses the guarantees of the lock (causing a race condition).

A general-purpose solution does not exist, but it is possible to solve the problem for a specific event by imposing special requirements on the user. Some of the solutions above may work if one places restrictions on the event handlers.

Solutions 2 or 3 work if the event handler is coded to handle the situation where it is invoked after it has unsubscribed from the event. It is not difficult to write a handler this way; asynchronous callback contexts would help with the implementation. The drawback is that each event handler must include multithread-awareness code, which complicates the method.

Solution 4 may also be made to work if the event handler does not block on a thread that subscribes to or unsubscribes from that same event. For simplicity, APIs that take this route often just state that event handlers may not block. The drawback is that this can be difficult to guarantee, since many objects hide their locking logic from their callers.

Conclusion

A general-purpose solution does not exist, and all other solutions have serious drawbacks (placing severe restrictions on the actions available to the event handler).

For this reason, I recommend the same approach that Jon Skeet ends up recommending at the end of Delegates and Events: "don't do that", i.e., don't use events in a multithreaded fashion. If an event exists on an object, then only one thread should be able to subscribe to or unsubscribe from that event, and it's the same thread that will raise the event.

One nice side effect of this approach is that the code becomes much simpler:

public event MyEventHandler MyEvent;

protected virtual OnMyEvent(MyEventArgs args)
{
    if (this.MyEvent != null)
    {
        this.MyEvent(this, args);
    }
}

Efficiency freaks can go one step further and explicitly implement the backing field, add handler, and remove handler. By removing the default locking (which is useless), the code is more explicit but also more efficient:

private MyEventHandler myEvent;
public event MyEventHandler MyEvent
{
    add
    {
        this.myEvent += value;
    }

    remove
    {
        this.myEvent -= value;
    }
}

protected virtual OnMyEvent(MyEventArgs args)
{
    if (this.myEvent != null)
    {
        this.myEvent(this, args);
    }
}

Another side effect is that this type of event handling forces one towards Event-Based Asynchronous Programming (or something very similar to it). EBAP is a logical conclusion for asynchronous object design, yielding maximal reusability. EBAP is also more consistent with regards to normal object concurrency restrictions: "Public static members of this type are thread safe. Any instance members are not guaranteed to be thread safe." Events that can only be accessed by one thread follow this common pattern; the event, as an instance member, is not guaranteed to be thread safe.

A third side effect takes longer to realize: more correct communication among threads. Instead of various threads directly subscribing to events (which would be run on another thread anyway), one must implement some form of thread communication. This forces the programmer to more clearly state the requirements from each thread's perspective, and this in turn results in less buggy multithreading code. Usually, more appropriate ways for thread communciation are found. The event subscription model is naturally discarded as a thread communication method (due to its inherent unsuitability) in favor of much more proven design patterns. This will eventually result in more correct multithreading code, though the process requires a minor redesign.

A Final Note

As of this writing, it is still popular to promote solution 2 (copy the delegate before raising the event). However, I strongly discourage this practice; it makes the code more obscure, and provides a false sense of security because it does not solve the problem! It is far better to simply not have "thread-safe events".

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralSolution #5
quizug
23:59 28 Jun '09  
What you can say about this solution #5:

private MyEventHandler myEvent = delegate { };
public event MyEventHandler MyEvent
{
add
{
this.myEvent += value;
}

remove
{
this.myEvent -= value;
}
}

protected virtual OnMyEvent(MyEventArgs args)
{
this.myEvent(this, args);
}

GeneralRe: Solution #5
Stephen Cleary
8:58 29 Jun '09  
Well, it's definitely not threadsafe. Smile

You're avoiding a check for null by permanently subscribing to a noop. I've seen other people use this technique, but it doesn't really appeal to me personally.

-Steve
GeneralRe: Solution #5
quizug
0:41 30 Jun '09  
Could you explain in more detail? What type of problem is here?

NullReference?
this.myEvent is always non-null value, because you can't unsubscribe from delegate {} even in same class. Actually you can remove delegate this.myEvent = null, but it isn't good Smile

Raise unsubscribed event?
this.myEvent isn't cached in OnMyEvent call.

Locked threads?
mmm... I don't think so.

It would be nice if you wrote another article how to implement thread-safe communication without events!
P.S.
private MyEventHandler myEvent;
public event MyEventHandler MyEvent
{
add
{
this.myEvent += value;
}

remove
{
this.myEvent -= value;
}
}

Is property & field (instead public event field) have any benefit except the maintenance of OOP?
GeneralRe: Solution #5
Stephen Cleary
2:09 30 Jun '09  
Two separate threads subscribing or unsubscribing at about the same time could overwrite each other's subscription/unsubscription.

-Steve
GeneralCan you go in deep with more detail in example code?
ray_ronnared
1:52 23 Jun '09  
Thank for nice share, but I still not cleard about it. Seem like I must compare by meself with real coding for all solutions implement.


thank for advance
GeneralRe: Can you go in deep with more detail in example code?
Stephen Cleary
4:46 23 Jun '09  
Jon Skeet's paper has more complete examples.

Also, the Nito Async library[^] has a timer class that uses CallbackContext to determine if it should raise an event.

-Steve
GeneralRe: Can you go in deep with more detail in example code?
ray_ronnared
4:50 23 Jun '09  
thank a lot !


Dig it.
GeneralThe obvious problem with lock-based approaches
supercat9
10:53 22 Jun '09  
If there is anything less than 100% consistency in the programmers' choice of lock, locks will do nothing to prevent simultaneous and conflicting operations by routines using different locks.

I agree with the other respondent that from a practical standpoint, any event handler must be prepared to handle the case where it gets executed in one thread even after it has been unsubscribed in another.

I have my own pseudo-event class which holds weak references to objects of type iAct(of T); instead of using delegates, it invokes theObject.Action(of T). Despite being lock-free, I think it should be thread-safe. Subscriptions are held in a singly-linked list, and event unsubscription is handled by setting an "unsubscribed" flag in the list item. If the list traversal code used to fire an event observes that the "next" event has been unsubscribed, it will swing the pointer (using CompareExchange) to point to the following event. Since new events are only added at the start of the list (also using CompareExchange) there is no hazard of such pointer-swinging accidentally killing a newly-added item.

Incidentally, under my system the event subscribe method returns a reference to the subscription object; objects may be unsubscribed either using that object, or by having the Action function return True.

Obviously it's too late now to make have .Net events be handled using anything like such a scheme, but if they were, why wouldn't such a system constitute a "general-purpose thread-safe implementation"?
GeneralRe: The obvious problem with lock-based approaches
Stephen Cleary
4:43 23 Jun '09  
I believe your system would still allow an event to be executed after it has been unsubscribed by another thread. It's possible the raising thread may test the flag, another thread sets it, and then the raising thread invokes it.

Regarding the other respondent, I still think that forcing "any event handler must be prepared to handle the case where it gets executed in one thread even after it has been unsubscribed in another" is a burden on the end-user. Perhaps because I mainly develop code libraries, many of which are used by programmers who wouldn't know what a thread is if it bit them. So, I've pretty much had to standardize on EBAP (the "asynchronous components for people who don't know what asynchronous components are" design Wink ).
GeneralRe: The obvious problem with lock-based approaches
supercat9
6:42 23 Jun '09  
I believe your system would still allow an event to be executed after it has been unsubscribed by another thread. It's possible the raising thread may test the flag, another thread sets it, and then the raising thread invokes it.

The only way to prevent "late" calls to an event handler would be to cause an unsubscribing thread to block until the event handler completed. If two event handlers could attempt to unsubscribe each other (hardly an unreasonable scenario) such handling would produce deadlock in the event the handlers fired simultaneously. Not a good scenario.

If an event handler is itself thread-safe, there generally won't be any problem dealing harmlessly with any "late" event calls. Dealing with the possibility of an event arriving when an object is disposed is generally no worse than dealing with any of the other states an object might be in.

My point was that using a singly-linked list to handle event subscriptions yields a system which allows for subscriptions and unsubscriptions to be handled efficiently and without locks. It is a general-purpose thread-safe system subject only to the following constraint:
If, between the time an event is fired and all handlers are complete, another thread attempts to subscribe unsubscribe, the newly-subscribed or unsubscribed event handler may or may not be called.
Note that this is far better than attempting to immutable delegates without locks (with locks that are not used 100% consistently); in that scenario, attempts to subscribe or unsubscribe several handlers simultaneously may result in some subscriptions being silently dropped or unsubscribed events being permanently reinstated.

Further, I think my main point was also very important: if two threads use different locks to manage control of a resource, the locks may as well not exist.

BTW, am I the only guy who finds annoying the lack of an interface similar to iEnumerable that would allow for deletion of the current object being enumerated? Enumerate-and-purge would seem like it should be a common operation. Unfortunately, the vb-inspired "Collection" object is the only class I know of which would allow such a thing.
GeneralSolution #2 and #3 aren't wrong!
Daniel Grunwald
8:24 20 Jun '09  
That is, solution #2 is equal to #3 on Microsoft's .NET implemenation.
You do NOT need 'volatile'.

To understand why, you need to look into the memory model used in Microsoft.NET: http://msdn.microsoft.com/en-us/magazine/cc163715.aspx[^]
Microsoft .NET 2.0 has a pretty strong memory model, the rule "Reads and writes cannot be introduced." ensures that solution #2 helps against the NullReferenceException.
Note that the ECMA specification for .NET has a less strict memory model. In other .NET implementations, solution #2 might be equal to solution #1 and still throw a NullReferenceException - the compiler is allowed to optimize the local variable away!

Now for why I don't consider solution #2 or #3 wrong:
Their only problem is that they might raise event handlers that are already unsubscribed.
Why would this cause any problems?

Let's look at this code:
void Dispose()
{
source.Event -= Handler;
something.Dispose();
}
void Handler(object sender, EventArgs e)
{
something.DoAnything();
}


Raising the event handler after it was unsubscribed means we could call DoAnything on a disposed object.
But actually, we already have a race condition even if the event source didn't raise unsubscribed event handlers:
we might Dispose "something" while DoAnything is running (but BEFORE DoAnything is able to acquire any lock, so don't think about solving the problem inside "something").

Raising event handlers that were unsubscribed by another thread is NOT wrong. The bug is in the event receiver, not in the source!

The only way the even source could solve this problem is by locking during the whole duration of the callback. That's not desirable (likely to cause deadlocks).
But, it is perfectly possible for the receiver to handle the problem:
void Dispose()
{
lock(lockObj) {
isDisposed = true;
source.Event -= Handler;
something.Dispose();
}
}
void Handler(object sender, EventArgs e)
{
lock(lockObj) {
if (isDisposed)
return;
something.DoAnything();
}
}

Now the lock is visible in the receiver code, so it's a lot less likely you'll introduce deadlocks this way.

Note that a receiver actually doesn't have to know that the event can be raised after it was deregistered: from the receiver's point of view, it's indistinguishable from the case where the event was first raised, but a context switch occurred on the first line of his event handler (before he's able to take any lock).
For the event source, it's perfectly valid to raise an already-deregistered event handler: it can only cause a problem for event receivers that aren't thread-safe anyways!

Solution #2 and #3 are valid solutions for multi-threaded events.
But multi-threading is hard. Making the event source thread-safe isn't sufficient; the event receiver must be correct, too!
GeneralRe: Solution #2 and #3 aren't wrong!
Stephen Cleary
14:12 20 Jun '09  
Thanks for your reply. I think our disagreement stems mainly from terminology.

Actually, I agree with the first part of your message. My blog post (and Jon Skeet's article) were written from the perspective of the C# language itself. Microsoft does have stronger guarantees for the current versions of its CLR (including Silverlight). However, this was apparently an oversight, just because x86 had a strong memory model. Remember that Mono and Moonlight also exist, as well as a few commercial projects designed for non-x86 processors. In fact, Microsoft tried to loosen the memory model when they ported to IA-64, but too much code was written with incorrect assumptions; so much of their own BCL code broke that they actually had to tighten the memory model back up. [You've probably already read this, but Google "Double-Checked Locking Is Broken" if you haven't heard of it].

So, I agree that #2 and #3 are equivalent on Microsoft's .NET implementation. You're also correct in that #1 and #2 may be equivalent on other implementations. If we allow for alternate implementations, #3 should be the "universally accepted" way.

You are correct in stating (and giving an example) that using #3 and placing additional restrictions on the handlers will result in threadsafe events. I did point out near the end of my post that "Solutions 2 or 3 work if the event handler is coded to handle the situation where it is invoked after it has unsubscribed from the event", and then I pointed out asynchronous callback contexts as one way to achieve this.

However, there is no general-purpose solution (meaning "no restrictions on event handlers"). My blog post was a response to:
1) The C# standard claiming to make events "thread-safe" just by locking "this".
2) A Microsoft trainer I heard recently (at a WPF/MVVM presentation) go into detail explaining why you needed solution #2 and how it made events "thread-safe"... never mentioning the need for additional synchronization in the handler code.

There is no way to implement event subscription + unsubscription + raising in a way that takes care of all the thread safety. One always has to place restrictions on the event handler as well.

If the restrictions are clearly documented, I don't have a problem with it. But there's no such thing as an event on an object that is, in and of itself, "thread-safe". It requires a specific type of cooperation from the handler as well.

For this reason, I strongly recommend against writing partially thread-safe events. It is much clearer if all event-based code is single-threaded, such as what is done by EBAP.


Last Updated 20 Jun 2009 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010