49

In order to raise an event we use a method OnEventName like this:

protected virtual void OnSomethingHappened(EventArgs e) 
{
    EventHandler handler = SomethingHappened;
    if (handler != null) 
    {
        handler(this, e);
    }
}

But what is the difference with this one ?

protected virtual void OnSomethingHappened(EventArgs e) 
{
    if (SomethingHappened!= null) 
    {
        SomethingHappened(this, e);
    }
}

Apparently the first is thread-safe, but why and how ?

It's not necessary to start a new thread ?

Jérémie Bertrand
  • 3,025
  • 3
  • 44
  • 53

10 Answers10

57

There is a tiny chance that SomethingHappened becomes null after the null check but before the invocation. However, MulticastDelagates are immutable, so if you first assign a variable, null check against the variable and invoke through it, you are safe from that scenario (self plug: I wrote a blog post about this a while ago).

There is a back side of the coin though; if you use the temp variable approach, your code is protected against NullReferenceExceptions, but it could be that the event will invoke event listeners after they have been detached from the event. That is just something to deal with in the most graceful way possible.

In order to get around this I have an extension method that I sometimes use:

public static class EventHandlerExtensions
{
    public static void SafeInvoke<T>(this EventHandler<T> evt, object sender, T e) where T : EventArgs
    {
        if (evt != null)
        {
            evt(sender, e);
        }
    }
}

Using that method, you can invoke the events like this:

protected void OnSomeEvent(EventArgs e)
{
    SomeEvent.SafeInvoke(this, e);
}
Fredrik Mörk
  • 155,851
  • 29
  • 291
  • 343
  • Thanks for your great answer (and blog post). – Jérémie Bertrand Sep 08 '10 at 15:13
  • 1
    I also got this extension method in my core library with exactly same name, do the same job in the exactly same way! My parameter name is eventHandler though. – tia Sep 08 '10 at 19:19
  • -1: `There is a back side of the coin though; if you use the temp variable approach (...) it could be that the event will invoke event listeners after they have been detached from the event`: this is **always** a possibility; it is unavoidable. – ANeves Feb 20 '14 at 11:57
  • 2
    There is an error in the blog post you reference: The final word in your quirk explanation should be 'detached' not 'attached'. – rism Oct 05 '14 at 12:03
  • Thanks for sharing that observation, @rism! Corrected the text. – Fredrik Mörk Oct 05 '14 at 16:34
  • 4
    It might be worth mentioning that with C# 6.0 we can now use the "elvis operator" (aka the null propagating operator) to raise events safely. `SomeEvent?.Invoke(sender, args);` – Tim Long Nov 10 '15 at 10:20
  • @FredrikMörk - blog post is 404ing now, possibly a new location? – Josh Mc Mar 09 '17 at 20:40
  • Thanks for pointing that out @JoshMc! Fixed now, and available at new location (automatically redirected). – Fredrik Mörk Mar 10 '17 at 06:02
47

Since C# 6.0 you can use monadic Null-conditional operator ?. to check for null and raise events in easy and thread-safe way.

SomethingHappened?.Invoke(this, args);

It’s thread-safe because it evaluates the left-hand side only once, and keeps it in a temporary variable. You can read more here in part titled Null-conditional operators.

Update: Actually Update 2 for Visual Studio 2015 now contains refactoring to simplify delegate invocations that will end up with exactly this type of notation. You can read about it in this announcement.

Krzysztof Branicki
  • 7,417
  • 3
  • 38
  • 41
15

I keep this snippet around as a reference for safe multithreaded event access for both setting and firing:

    /// <summary>
    /// Lock for SomeEvent delegate access.
    /// </summary>
    private readonly object someEventLock = new object();

    /// <summary>
    /// Delegate variable backing the SomeEvent event.
    /// </summary>
    private EventHandler<EventArgs> someEvent;

    /// <summary>
    /// Description for the event.
    /// </summary>
    public event EventHandler<EventArgs> SomeEvent
    {
        add
        {
            lock (this.someEventLock)
            {
                this.someEvent += value;
            }
        }

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

    /// <summary>
    /// Raises the OnSomeEvent event.
    /// </summary>
    public void RaiseEvent()
    {
        this.OnSomeEvent(EventArgs.Empty);
    }

    /// <summary>
    /// Raises the SomeEvent event.
    /// </summary>
    /// <param name="e">The event arguments.</param>
    protected virtual void OnSomeEvent(EventArgs e)
    {
        EventHandler<EventArgs> handler;

        lock (this.someEventLock)
        {
            handler = this.someEvent;
        }

        if (handler != null)
        {
            handler(this, e);
        }
    }
Jesse C. Slicer
  • 19,901
  • 3
  • 68
  • 87
  • 2
    Yep, you and I are on the same page. The accepted answer has a subtle memory barrier problem that our solution resolves. Using custom `add` and `remove` handlers is probably unnecessary since the compiler emits the locks in automatic implementations. Though, I thought I remember something changed with that in .NET 4.0. – Brian Gideon Sep 08 '10 at 16:21
  • @Brian - agreed, though pre-4.0, the locks are on the `this` object, which means that code external to the class can wind up baffling the mechanism by locking on an instance. Jon Skeet provided the inspiration here http://csharpindepth.com/Articles/Chapter2/Events.aspx#threading . – Jesse C. Slicer Sep 08 '10 at 16:42
  • Great link. And yes, I acknowldge all the nuances with locking on `this`. Anyone have a quick link for the changes in .NET 4.0? If not, I'll just pull up the specification. – Brian Gideon Sep 08 '10 at 17:43
  • Here's a decent little article: http://blogs.msdn.com/b/cburrows/archive/2010/03/05/events-get-a-little-overhaul-in-c-4-part-i-locks.aspx – Jesse C. Slicer Sep 09 '10 at 02:18
  • 1
    I'm wondering why not just raise the handler while under the lock (don't need to copy either)? Then it should impossible to call the unsubscriber after he has unsubscribed. Sure, the tradeoff is that the subs/unsubs are blocked during the raise but at least there is no pesky race condition. Do I understand correctly that the accepted answer is claiming that this scenario cannot be handled? – crokusek Oct 19 '13 at 03:38
  • @crokusek: _"I'm wondering why not just raise the handler while under the lock"_ -- it would cause more problems than it solves. It's true that doing so would resolve the otherwise-irreconcilable race condition. But holding the lock while invoking arbitrary event handlers makes the execution considerably more complicated and introduces the opportunity for deadlock that otherwise wouldn't exist. The race condition is almost never an actual problem, and where it is, the owner of the event handler can do their own work to resolve it (e.g. ignoring invocations after its unsubscribed). – Peter Duniho Mar 14 '20 at 04:02
  • @PeterDuniho, we just may have to disagree. the race-condition seems important since caller could Unsubscribe() and Dispose() itself causing the callback to raise an exception. I guess the deadlock of which you speak would be if the callback jumps threads (e.g. using Dispatch()) and that code subscribes/unsubscribes. Okay. But requiring callback code to properly ignore callbacks requires another lock and what if the callback code were shared among multiple distributers, requiring some identifier to distinguish. Bandaid gets big. – crokusek Mar 19 '20 at 22:51
  • @crokusek: _"what if the callback code were shared among multiple distributers"_ -- I don't know what you mean there, but certainly in my experience it has been no trouble at all for a subscriber to guard against event notifications after unsubscription, when necessary (indeed, in the vast majority of cases, it's not necessary...the best approach is to not implement an event handler that needs to guard against post-unsubscribe invocations in the first place). In any case, whatever you think about the issue, the answer above does nothing to address it (it only ensures consistent field state). – Peter Duniho Mar 19 '20 at 23:00
13

For .NET 4.5 it's better to use Volatile.Read to assign a temp variable.

protected virtual void OnSomethingHappened(EventArgs e) 
{
    EventHandler handler = Volatile.Read(ref SomethingHappened);
    if (handler != null) 
    {
        handler(this, e);
    }
}

Update:

It's explained in this article: http://msdn.microsoft.com/en-us/magazine/jj883956.aspx. Also, it was explained in Fourth edition of "CLR via C#".

Main idea is that JIT compiler can optimize your code and remove the local temporary variable. So this code:

protected virtual void OnSomethingHappened(EventArgs e) 
{
    EventHandler handler = SomethingHappened;
    if (handler != null) 
    {
        handler(this, e);
    }
}

will be compiled into this:

protected virtual void OnSomethingHappened(EventArgs e) 
{
    if (SomethingHappened != null) 
    {
        SomethingHappened(this, e);
    }
}

This happens in certain special circumstances, however it can happen.

rpeshkov
  • 4,877
  • 3
  • 27
  • 43
  • I didn't know the use of Volatile for that. Can you explain a little bit why it's better? – Jérémie Bertrand Jun 18 '13 at 11:33
  • Added explanation to my answer. – rpeshkov Jun 18 '13 at 16:23
  • Again, it only protects from the NullReferenceException, it has nothing to do with a more serious problem of the race condition, where you invoke the event handlers that have just unsubscribed. – KumoKairo Oct 11 '14 at 19:25
  • I'm not clear between whether Volatile.Read is safe enough, or whether you should use Interlocked.CompareExchange(ref SomethingHappened, null, null)?.Invoke(this, e); https://codeblog.jonskeet.uk/2015/01/30/clean-event-handlers-invocation-with-c-6/ – bopapa_1979 Apr 12 '17 at 19:51
  • Is it possible to write unit test for this situation? – Pavel Sapehin Jul 26 '17 at 07:10
7

Declare your event like this to get thread safety:

public event EventHandler<MyEventArgs> SomethingHappened = delegate{};

And invoke it like this:

protected virtual void OnSomethingHappened(MyEventArgs e)   
{  
    SomethingHappened(this, e);
} 

Although the method is not needed anymore..

Update 2021-09-01

Today I would simply do (which do not require the emty delegate):

SomethingHappened?.Invoke(e);

Someone pointed out that using an empty delegate has a larger overhead. That's true. But from an application perspetive, the performance impact is minimal. Therefore, it's much more important to choose the solution that has the cleanest code.

jgauffin
  • 99,844
  • 45
  • 235
  • 372
  • 1
    This might cause some performance problems if you have a lot of events firing, since the empty delegate will be executed every time regardless of whether there are legitimate subscribers to the event or not. It's a small overhead, but can theoretically add up. – Adam Lear Sep 08 '10 at 15:04
  • 9
    It's a *very* small overhead. There are bigger problems in your code that should be optimized first. – jgauffin Sep 08 '10 at 17:37
  • this guard against nullreference error, but not strictly threadsafe, see discussion https://stackoverflow.com/questions/786383/c-sharp-events-and-thread-safety – Ben Jul 29 '18 at 10:10
  • But it's a very big overhead if you compare calling two combined delegates (`{ }` and a subscriber) with calling only one (the subscriber) – Vlad Aug 31 '21 at 18:07
  • Eleven year old answer :) Today I would simply do `SomethingHappened?.Invoke(e);`. Regarding the overhead, yes it's large compared to the alternative. From an application perspective it's not relevant at all since events are not called often so the performance loss is not measurable and you'll have must more important performance issues in your application. Simply choose the cleanest code in this example. – jgauffin Sep 01 '21 at 07:04
7

It depends on what you mean by thread-safe. If your definition only includes the prevention of the NullReferenceException then the first example is more safe. However, if you go with a more strict definition in which the event handlers must be invoked if they exist then neither is safe. The reason has to do with the complexities of the memory model and barriers. It could be that there are, in fact, event handlers chained to the delegate, but the thread always reads the reference as null. The correct way of fixing both is to create an explicit memory barrier at the point the delegate reference is captured into a local variable. There are several ways of doing this.

  • Use the lock keyword (or any synchronization mechanism).
  • Use the volatile keyword on the event variable.
  • Use Thread.MemoryBarrier.

Despite the awkward scoping problem which prevents you from doing the one-line initializer I still prefer the lock method.

protected virtual void OnSomethingHappened(EventArgs e)           
{          
    EventHandler handler;
    lock (this)
    {
      handler = SomethingHappened;
    }
    if (handler != null)           
    {          
        handler(this, e);          
    }          
}          

It is important to note that in this specific case the memory barrier problem is probably moot because it is unlikely that reads of variables will be lifted outside method calls. But, there is no guarentee especially if the compiler decides to inline the method.

Brian Gideon
  • 47,849
  • 13
  • 107
  • 150
3

Actually, the first is thread-safe, but the second isn't. The problem with the second is that the SomethingHappened delegate could be changed to null between the null verification and the invocation. For a more complete explanation, see http://blogs.msdn.com/b/ericlippert/archive/2009/04/29/events-and-races.aspx.

Nicole Calinoiu
  • 20,843
  • 2
  • 44
  • 49
1

I tried to pimp out Jesse C. Slicer's answer with:

  • Ability to sub/unsubscribe from any thread while within a raise (race condition removed)
  • Operator overloads for += and -= at the class level
  • Generic caller defined delegates

    public class ThreadSafeEventDispatcher<T> where T : class
    {
        readonly object _lock = new object();
    
        private class RemovableDelegate
        {
            public readonly T Delegate;
            public bool RemovedDuringRaise;
    
            public RemovableDelegate(T @delegate)
            {
                Delegate = @delegate;
            }
        };
    
        List<RemovableDelegate> _delegates = new List<RemovableDelegate>();
    
        Int32 _raisers;  // indicate whether the event is being raised
    
        // Raises the Event
        public void Raise(Func<T, bool> raiser)
        {
            try
            {
                List<RemovableDelegate> raisingDelegates;
                lock (_lock)
                {
                    raisingDelegates = new List<RemovableDelegate>(_delegates);
                    _raisers++;
                }
    
                foreach (RemovableDelegate d in raisingDelegates)
                {
                    lock (_lock)
                        if (d.RemovedDuringRaise)
                            continue;
    
                    raiser(d.Delegate);  // Could use return value here to stop.                    
                }
            }
            finally
            {
                lock (_lock)
                    _raisers--;
            }
        }
    
        // Override + so that += works like events.
        // Adds are not recognized for any event currently being raised.
        //
        public static ThreadSafeEventDispatcher<T> operator +(ThreadSafeEventDispatcher<T> tsd, T @delegate)
        {
            lock (tsd._lock)
                if (!tsd._delegates.Any(d => d.Delegate == @delegate))
                    tsd._delegates.Add(new RemovableDelegate(@delegate));
            return tsd;
        }
    
        // Override - so that -= works like events.  
        // Removes are recongized immediately, even for any event current being raised.
        //
        public static ThreadSafeEventDispatcher<T> operator -(ThreadSafeEventDispatcher<T> tsd, T @delegate)
        {
            lock (tsd._lock)
            {
                int index = tsd._delegates
                    .FindIndex(h => h.Delegate == @delegate);
    
                if (index >= 0)
                {
                    if (tsd._raisers > 0)
                        tsd._delegates[index].RemovedDuringRaise = true; // let raiser know its gone
    
                    tsd._delegates.RemoveAt(index); // okay to remove, raiser has a list copy
                }
            }
    
            return tsd;
        }
    }
    

Usage:

    class SomeClass
    {   
        // Define an event including signature
        public ThreadSafeEventDispatcher<Func<SomeClass, bool>> OnSomeEvent = 
                new ThreadSafeEventDispatcher<Func<SomeClass, bool>>();

        void SomeMethod() 
        {
            OnSomeEvent += HandleEvent; // subscribe

            OnSomeEvent.Raise(e => e(this)); // raise
        }

        public bool HandleEvent(SomeClass someClass) 
        { 
            return true; 
        }           
    }

Any major problems with this approach?

The code was only briefly tested and edited a bit on insert.
Pre-acknowledge that List<> not a great choice if many elements.

Community
  • 1
  • 1
crokusek
  • 5,345
  • 3
  • 43
  • 61
1

Actually, no, the second example isn't considered thread-safe. The SomethingHappened event could evaluate to non-null in the conditional, then be null when invoked. It's a classic race condition.

Curt Nichols
  • 2,757
  • 1
  • 17
  • 24
0

For either of these to be thread safe, you are assuming that all the objects that subscribe to the event are also thread safe.

Martin Brown
  • 24,692
  • 14
  • 77
  • 122