I'm using an object (EventReceiver
) that registers a member to an event of an object (EventSource
) imported via ctor. The EventReceiver
implements IDisposable
and unsubscribes itself from the EventSource
.
The problem is that there are different threads that invoke the event handler and disposes the EventReceiver
. The event will be called after the unsubscribe is done. There's a race condidition between event raising and unsubscribing.
How could that be solved?
Here's a sample implementation that demonstrates the problem:
internal class Program
{
private static void Main(string[] args)
{
var eventSource = new EventSource();
Task.Factory.StartNew(
() =>
{
while (true)
{
eventSource.RaiseEvent();
}
});
Task.Factory.StartNew(
() =>
{
while (true)
{
new EventReceiver(eventSource).Dispose();
}
});
Console.ReadKey();
}
}
public class EventSource
{
public event EventHandler<EventArgs> SampleEvent;
public void RaiseEvent()
{
var handler = this.SampleEvent;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
}
public class EventReceiver : IDisposable
{
private readonly EventSource _source;
public EventReceiver(EventSource source)
{
this._source = source;
this._source.SampleEvent += this.OnSampleEvent;
}
public bool IsDisposed { get; private set; }
private void OnSampleEvent(object sender, EventArgs args)
{
if (this.IsDisposed)
{
throw new InvalidOperationException("This should never happen...");
}
}
public void Dispose()
{
this._source.SampleEvent -= this.OnSampleEvent;
this.IsDisposed = true;
}
}
The exception will be thrown nearly direct after program start on a multi core processor. Yes I know that var handler = this.SampleEvent
will create a copy of the event handler and that causes the problem.
I tried to implement the RaiseEvent
method like that but it doesn't help:
public void RaiseEvent()
{
try
{
this.SampleEvent(this, EventArgs.Empty);
}
catch (Exception)
{
}
}
The question is: How to implement threadsafe registering to and unregistering from events in a multithreaded manner?
My expectation was that the unregistering will be suspended until the current fired event has been finished (meybe this could only work using the second implementation). But I was disappointed.
If you've got two threads - one calling the event handlers, and one unsubscribing - there will always be a race condition, where you end up with the following ordering:
That's inevitable with the immutability of delegates in .NET. Even with some sort of thread-safe, mutable delegate type there'd always be a finer-grained race condition:
Worse, you could unsubscribe while the handler is executing - what would you expect to happen then? Would you expect the handler code to be arbitrarily terminated?
You can easily implement your own check for "am I really meant to run at this point" within the handler, but you'll need to work out your own semantics. You could use locking to make sure that the handler unsubscription can't occur while the handler is executing, but that feels pretty dangerous to me, unless you know that your event handler will complete in a short time frame.
See more on this question at Stackoverflow