adspx5
adspx5

Reputation: 771

Adding and Removing event handlers for multithreaded applications

I have a question how to properly add/remove event handler in multithreaded environment for asynchronous callbacks.

I have MyCore class which receives asynchronous callbacks from ProxyDLL which dispatches callback from unmanaged code. I have form(managed) which subscribes to events.

What would be the right approach to attaching/detaching from event. I noticed that MulticastDelegate has _invocationcount. What it does? Does inner logic of the event blocks detaching from event if callback call is in process until callback is done? Is _invocationcount exists for that puprose? Is detathing from event (in general) is treadsafe?

class Form1
{
  EventHandler m_OnResponse;
  Int32 m_SomeValue;
  Form1()
  {
    m_OnResponse = new EventHandler(OnResponseImpl);
    m_MyCore.SetCallBackOnLogOn(m_OnResponse);
  }
  ~Form1()
  {
    m_MyCore.ReleaseCallBackOnLogOn(m_OnResponse);
  }
  private OnResponseImpl(object sender, EventArgs e)
  {
    Thread.Sleep(60*1000);

    m_SomeValue = 1;             // <<-- How to/Who guarantees that Form1 obj is still
                                 // alive. May be callback was invoked earlier and
                                 // we just slept too long

    if (!this.IsDisposed)
    {
        invokeOnFormThread(DoOnResponseImpl, sender, e);
    }
  }
}

class MyCore
{
  private event EventHandler OnLogOn;
  public void SetCallBackOnLogOn(EventHandler fn)
  {
    // lock (OnLogOn)
    {
        OnLogOn += fn;
    }
  }
  ReleaseCallBackOnLogOn(EventHandler fn)
  {
    // lock (OnLogOn)
    {
        OnLogOn -= fn;
    }
  }
  public void DoDispatchOnLogOn()
  {
    // lock (OnLogOn)
    {
      if (OnLogOn != null)
      {
        OnLogOn(this, null);
      }
    }
  }
}

Upvotes: 2

Views: 534

Answers (1)

Brian Gideon
Brian Gideon

Reputation: 48949

Default event add and remove operations are already thread-safe. You do not need worry about that part in most cases. It is the invocation of the multicast delegate that you need to worry about.

public void DoDispatchOnLogOn()
{
  EventHander local;
  lock (this)
  {
    local = OnLogOn;
  }
  if (local != null)
  {
    local(this, null);
  }
}

What I did here was to create a local variable that will hold the OnLogOn delegate chain. I am exploiting the immutability of multicast delegates here so that we can do a thread-safe check for null and the invoke sequence. The lock is only used to ensure a "fresh" read of OnLogon and it is strictly optional if you do not mind gettting a "stale" read.

Update:

I need to be sure that callback is not invoked when ReleaseCallBackOnLogOn ended unsubscribing delegate.

For the most part the code I have will not attempt to execute any event handlers that have been unsubscribed. There is a slight a race between removing an event handler and raising the event which could cause the event handler to get executed even though it was just recently removed.

I need to be sure that my class instance is still alive, until call to callback is fully done.

Delegates hold a reference to the class instance containing the target method. This will keep your instance rooted and thus not eligible for garbage collection. You do not need to worry about this.

I should point out that the ~Form1 finalizer is not a good a place to remove event handlers. Remember, delegates hold a reference to the instance containing the target method so in most cases that finalizer will not get called and the event handler will not be removed from the event.

Upvotes: 3

Related Questions