Tuesday, September 29, 2009

Thread-safe general delegate invoke method

When dealing with events and delegates in a UI with worker threads you have to check if check if invocation is required in the eventhandler.
Instead of doing the test in all eventhandlers I made a general Invoke method to handle this.
When invocation is not required this method is slower, but when invocation is required it´s faster.
The thing for me is that if I always use this method I don´t have to worry about dealing with this in every eventhandler or when raising events.

public static class ThreadUtility
{
 public static void Invoke(Delegate source, params object[] args)
 {
  if (source == null) return;
  int paramCount = source.Method.GetParameters().Length;
  if (paramCount != args.Length) throw (new ArgumentException(string.Format("Invalid number of parameters. {0} was expected, {1} was supplied", paramCount, args.Length)));
  foreach (Delegate invocationItem in source.GetInvocationList())
  {
   if (IsDisposed(invocationItem.Target)) continue;
   ISynchronizeInvoke target = invocationItem.Target as ISynchronizeInvoke;
   if (target != null && target.InvokeRequired)
   {
    target.BeginInvoke(invocationItem, args);
   }
   else
   {
    invocationItem.DynamicInvoke(args);
   }
  }
 }

 private static bool IsDisposed(object target)
 {
  PropertyInfo propertyInfo = target.GetType().GetProperty("IsDisposed");
  if (propertyInfo == null) return (false);
  return ((bool)propertyInfo.GetValue(target, null));
 }
}

This is how it looks the old way...

public event EventHandler ThreadUtilTest;
public void OnThreadUtilTest()
{
 EventHandler temp = ThreadUtilTest;
 if (temp != null)
 {
  temp(this, new EventArgs());
 }
}

private void ThreadUtilTestHandler(object sender, EventArgs e)
{
 if (InvokeRequired)
 {
  Invoke(temp, sender, e);
  return;
 }
 // Execute operation
}

...and this is how it looks the with my method...

public event EventHandler ThreadUtilTest;
public void OnThreadUtilTest()
{
 ThreadUtility.Invoke(ThreadUtilTest, this, new EventArgs());
}

private void ThreadUtilTestHandler(object sender, EventArgs e)
{
 // Execute operation
}

When using events you usualy don´t want to add multiple instances of the same eventhandlers for the same object.
Here´s a way to avoid that...

private object _eventSychLock = new object();
private EventHandler _event;
public event EventHandler ThreadUtilTest
{
 add
 {
  lock (_eventSychLock)
  {
   if (!ThreadUtility.InInvocationList(_event, value))
   {
    _event += value;
   }
  }
 }
 remove
 {
  lock (_eventSychLock)
  {
   _event -= value;
  }
 }
}

public void OnThreadUtilTest()
{
 ThreadUtility.Invoke(_event, this, new EventArgs());
}

public static class ThreadUtility
{
 public static bool InInvocationList(Delegate source, Delegate subscriber)
 {
  if (source == null) return (false);
  if (subscriber == null) return (true);
  foreach (Delegate invocationItem in source.GetInvocationList())
  {
   if (invocationItem == subscriber) return (true);
  }
  return (false);
 }
}

No comments:

Post a Comment