Archives
Blog Roll
Feed your Head
Random Thought Patterns
a Xavier Musy weblog production


Sunday, June 06, 2004
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
 
Weak Events

There's a not entirely uncommon WinForms scenario whereby a long-lived event publisher has short-lived event subscribers, in which the subscriber(s) mistakenly never detach from the events, thereby causing memory leaks (can't be GC'ed), or worse (exceptions or resurrection caused by the handlers in the disposed subscribers).

A partial solution to this problem is to use weak events, such that the GC can occur on the subscribers. It is only a partial solution, because the short-lived subscribers hang around until GC occurs; and, GC typically isn’t explicitly done. Even if it is, it would assume the publisher knows when a subscriber needs to detach or is disposed, which isn’t likely.

Greg Schechter blogs about a specific related scenario in Avalon, and posts an interesting solution to the problem. Ian Griffiths provides an alternative solution using generics and weak handlers (instead of weak delegates).

I’ve written an alternate solution below. It also addresses 2 other common problems: subscribers subscribing to the same handler more than once (solved using the CombineUnique method), and subscribers throwing unhandled exceptions, thereby stopping any remaining subscribers in the invocation list from receiving the events (solved using the InvokeSafe method).




public class WeakMulticastDelegate
{
private WeakReference weakRef;
private MethodInfo method;
private WeakMulticastDelegate prev;

public WeakMulticastDelegate(Delegate realDelegate)
{
this.weakRef = new WeakReference(realDelegate.Target);
this.method = realDelegate.Method;
}


public static WeakMulticastDelegate Combine(WeakMulticastDelegate weakDelegate, Delegate realDelegate)
{
if ( realDelegate == null )
return null;
return ( weakDelegate == null ) ? new WeakMulticastDelegate(realDelegate) : weakDelegate.Combine(realDelegate);
}


public static WeakMulticastDelegate CombineUnique(WeakMulticastDelegate weakDelegate, Delegate realDelegate)
{
if ( realDelegate == null )
return null;
return ( weakDelegate == null ) ? new WeakMulticastDelegate(realDelegate) : weakDelegate.CombineUnique(realDelegate);
}

private WeakMulticastDelegate Combine(Delegate realDelegate)
{
WeakMulticastDelegate head = new WeakMulticastDelegate( realDelegate );
head.prev = this.prev;
this.prev = head;
return this;
}

// provides unique subscribers: makes sure the the target & method are not already a subscriber
private WeakMulticastDelegate CombineUnique(Delegate realDelegate)
{
bool found = false;
if ( prev != null )
{
WeakMulticastDelegate curNode = prev;
while (!found && curNode != null )
{
if ( curNode.weakRef.IsAlive && curNode.weakRef.Target == realDelegate.Target &&
curNode.method == realDelegate.Method )
{
found = true;
}
curNode = curNode.prev;
}
}
return found ? this : Combine(realDelegate);
}

public static WeakMulticastDelegate Remove(WeakMulticastDelegate weakDelegate, Delegate realDelegate)
{
if ( realDelegate == null || weakDelegate == null )
return null;
return weakDelegate.Remove(realDelegate);
}

private WeakMulticastDelegate Remove(Delegate realDelegate)
{
if ( weakRef.IsAlive &&
weakRef.Target == realDelegate.Target &&
method == realDelegate.Method )
{
return this.prev;
}

WeakMulticastDelegate current = this.prev;
WeakMulticastDelegate last = this;
while ( current != null )
{
if ( current.weakRef.IsAlive &&
current.weakRef.Target == realDelegate.Target &&
current.method == realDelegate.Method )
{
last.prev = current.prev;
current.prev = null;
break;
}
last = current;
current = current.prev;
}

return this;

}

public void Invoke(object[] args)
{
WeakMulticastDelegate current = this;
while ( current != null )
{
if ( current.weakRef.IsAlive )
{
current.method.Invoke(current.weakRef.Target, args);
}
current = current.prev;
}
}

// provides safe invocation: such that a subscriber throwing in it's handler won't stop
// other subscribers from receiving
public void InvokeSafe(object[] args)
{
WeakMulticastDelegate current = this;
while ( current != null )
{
if ( current.weakRef.IsAlive )
{
try
{
current.method.Invoke(current.weakRef.Target, args);
}
catch(Exception ex)
{
Console.WriteLine(ex);
}
}
current = current.prev;
}
}

}



A publisher can expose an event in the following manner:


private WeakMulticastDelegate weakEventHandler = null;

public event EventHandler SomeEvent
{
add
{
weakEventHandler = WeakMulticastDelegate.CombineUnique(weakEventHandler, value);
}
remove
{
weakEventHandler = WeakMulticastDelegate.Remove(weakEventHandler, value);
}
}


And can fire the event this way:


if ( this.weakEventHandler != null )
weakEventHandler.Invoke( new object[]{this, new EventArgs()} );



Powered by Blogger

The information in this weblog is provided "AS IS" with no warranties, and confers no rights. This weblog does not represent the thoughts, intentions, plans or strategies of Seed Industries. It is solely my opinion.
Copyright © 2003, Xavier Musy. All right are reserved.