I'm using an object (EventReceiver) that registers a member to an event of an object (EventSource) imported via ctor. The EventReceiverimplements 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.