Preventing Recursive Events

From eqqon

Jump to: navigation, search

Preventing Recursive Events in C#

Events that cause each other to be fired recursively are usually a problem when two classes need to sync data. In large programs these recursive call chains can be quite long and thus a recursion might not be expected when firing some event. The recursion ends in a StackOverflowException. There are many possibilities to resolve such a problem. The easiest one is to introduce a boolean locking field which prevents reentrancy:


if(MyEvent != null)
{
    m_MyEvent_lock=true
    MyEvent();
    m_MyEvent_lock=false
}

This is fine unless you need to lock a lot of events and the locking fields are spamming your classes. Another way to cope with recursive events is the Mediator Pattern by [Gamma et al 95]. If there are a number of objects which need to keep data in sync they don't notify each other via events but notify a common mediator. The mediator then notifies the others and makes sure not to notify the object that has notified him. It is often the best with technical problems to rethink the design (i.e. apply the Mediator Pattern) but sometimes we just want an elegant technical solution.

EventLocker

An elegant technical solution to the recursive event problem is my EventLocker. It keeps a Hashtable of currently invoked (=locked) delegates and stops recursive invocations. The EventLocker keeps your classes clean of locking noise. However, you should only use it for potentially recursive event invocations because dynamically invoking a delegate is inherently slower than normal typesafe event invocation.


public class EventLocker
{
    static Hashtable m_lock;
    static EventLocker()
    {
        m_lock = new Hashtable();
    }

    public static void Invoke(Delegate d, params object[] args)
    {
        if (d != null && !m_lock.ContainsKey(d))
        {
            m_lock.Add(d, true);
            d.DynamicInvoke(args);
            m_lock.Remove(d);
        }
    }

    public static void InvokeThreadsafe(Delegate d, params object[] args)
    {
        lock (m_lock) Invoke(d, args);
    }
}

--Henon 23:45, 21 November 2007 (CET)