Why doesn’t Dispatcher implement ISynchronizeInvoke?

This is a rant.  I don’t have the answer to the question.

So, the latest project I’m working on is a kiosk app in WPF that has to interact with hardware.  The hardware has various needs; some of it I need to poll, and others I check on status every given interval.  Every class I’ve created for interacting with the hardware has a SynchronizingObject property, just like the System.Timers.Timer class, and I was pretty happy with myself when I figured out that my event raising implementation was the same as that class’s.

The SynchronizingObject property looks like this:

1 : public ISynchronizeInvoke SynchronizingObject 2 : {
  3 : get {
    return m_syncObj;
  }
  4 : set {
    m_syncObj = value;
  }
  5:
}

Pretty straightforward.  To call an event it might be something like this:

1 : protected virtual void OnStatusChanged(EventArgs e) 2 : {
  3 : if (StatusChanged != null) 4 : {
    5 : if (m_syncObj == null || !m_syncObj.InvokeRequired) 6
        : StatusChanged(this, e);
    7 : else 8 : m_syncObj.BeginInvoke(StatusChanged, new object[] { this, e });
    9:
  }
  10:
}

Easy?  Good.

Well, like apparently everything straightforward about Windows Forms programming, it’s been changed for Windows Presentation Foundation.  Each Visual element has an associated Dispatcher; Dispatchers are created per-thread, and like in Windows Forms, you can’t update a Visual or its descendent tree from another thread.  And the Dispatcher class almost looks like it would be interface-compatible with ISynchronizeInvoke with a couple exceptions.  I thought, “great!  I’ll be able to just create a simple Adapter class!”  Nope.  Well, it wasn’t simple, that’s for sure.

Let’s take a look at the overload list for Invoke and you might see why.  Invoked delegates either take one argument or one argument plus a params list of arguments.  Then you think…. what?

There are some issues with params lists.  For example, let’s say that you’re passing an argument that is an object[].  Should that get expanded?  Consider this code:

1 : void DoStuff(params object[] args) {
  ...
}
2 : 3 :  // now within a function
         4 : object[] values = new object[] { 1, "stuff", 25.0 };
5 : DoStuff(values, "something else?");

The kind of difficulty we run into is — should “values” be expanded out or should it stay as an array?  In other words, should args be { 1, “stuff”, 25.0, “something else?” }, or should it be { { 1, “stuff”, 25.0 }, “something else?” }?  What about when it’s the only argument passed – what if DoStuff(values) is the call?  Then should args be {1, “stuff”, 25.0} or { {1, “stuff”, 25.0} }?  For the purposes of this application, I assumed that, in the first case, args would be like the latter; and in the second case, args would be like the former.

So here’s my test app — it’s a basic WPF application.  Here’s Window1.xaml:

1 : <Window x : Class = "DispatcherTest.Window1" 2 : xmlns =
         "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 : xmlns : x =
             "http://schemas.microsoft.com/winfx/2006/xaml" 4 : Title =
                 "Window1" Height = "300" Width = "300"> 5 : <Grid> 6
    : <Button Height = "23" Margin = "102,87,100,0" Name =
           "button1" VerticalAlignment = "Top" Click = "button1_Click">
          Button</ Button> 7 : </ Grid> 8 : </ Window>

and Window1.xaml.cs:

1 : public partial class Window1 : Window 2 : {
  3 : private ISynchronizeInvoke m_invoker;
  4 : 5 : public Window1() 6 : {
    7 : InitializeComponent();
   8:          m_invoker = new DispatcherWinFormsCompatAdapter(this.Dispatcher));
   9:
  }
  10 : 11 : private void button1_Click(object sender, RoutedEventArgs e) 12 : {
    13 : ThreadPool.QueueUserWorkItem(new WaitCallback(DoSomeLongRunningWork),
                                      null);
    14:
  }
  15 : 16 : private void DoSomeLongRunningWork(object state) 17 : {
    18 : Thread.Sleep(2000);
    19 : EventHandler updateButton = delegate(object sender, EventArgs e) 20 : {
      21 : button1.Content = "Clicked!";
      22:
    };
    23 : 24 : if (m_invoker.InvokeRequired) 25 : {
      26 : m_invoker.BeginInvoke(updateButton,
                                 new object[] { this, EventArgs.Empty });
      27:
    }
    28 : else 29 : {
      30 : updateButton(this, EventArgs.Empty);
      31:
    }
    32:
  }
  33:
}

Finally, here’s my skeleton adapter class.  Note that EndInvoke and Invoke are not used so I don’t implement them yet:

1 : internal class DispatcherWinFormsCompatAdapter : ISynchronizeInvoke 2 : {
  3 : #region IAsyncResult implementation 4
      : private class DispatcherAsyncResultAdapter : IAsyncResult 5 : {
    6 : private DispatcherOperation m_op;
    7 : private object m_state;
    8 : 9
        : public DispatcherAsyncResultAdapter(DispatcherOperation operation) 10
        : {
      11 : m_op = operation;
      12:
    }
    13 : 14 : public DispatcherAsyncResultAdapter(DispatcherOperation operation,
                                                  object state) 15
        : : this(operation) 16 : {
      17 : m_state = state;
      18:
    }
    19 : 20 : public DispatcherOperation Operation 21 : {
      22 : get {
        return m_op;
      }
      23:
    }
    24 : 25 : #region IAsyncResult Members 26 : 27 : public object AsyncState 28
        : {
      29 : get {
        return m_state;
      }
      30:
    }
    31 : 32 : public WaitHandle AsyncWaitHandle 33 : {
      34 : get {
        return null;
      }
      35:
    }
    36 : 37 : public bool CompletedSynchronously 38 : {
      39 : get {
        return false;
      }
      40:
    }
    41 : 42 : public bool IsCompleted 43 : {
      44 : get {
        return m_op.Status == DispatcherOperationStatus.Completed;
      }
      45:
    }
    46 : 47 : #endregion 48:
  }
  49 : #endregion 50 : private Dispatcher m_disp;
  51 : public DispatcherWinFormsCompatAdapter(Dispatcher dispatcher) 52 : {
    53 : m_disp = dispatcher;
    54:
  }
  55 : #region ISynchronizeInvoke Members 56 : 57
      : public IAsyncResult BeginInvoke(Delegate method, object[] args) 58 : {
    59 : if (args != null && args.Length > 1) 60 : {
      61 : object[] argsSansFirst = GetArgsAfterFirst(args);
      62 : DispatcherOperation op = m_disp.BeginInvoke(
               DispatcherPriority.Normal, method, args[0], argsSansFirst);
      63 : return new DispatcherAsyncResultAdapter(op);
      64:
    }
    65 : else 66 : {
      67 : if (args != null) 68 : {
        69 : return new DispatcherAsyncResultAdapter(m_disp.BeginInvoke(
                 DispatcherPriority.Normal, method, args[0]));
        70:
      }
      71 : else 72 : {
        73 : return new DispatcherAsyncResultAdapter(
                 m_disp.BeginInvoke(DispatcherPriority.Normal, method));
        74:
      }
      75:
    }
    76:
  }
  77 : 78 : private static object[] GetArgsAfterFirst(object[] args) 79 : {
    80 : object[] result = new object[args.Length - 1];
    81 : Array.Copy(args, 1, result, 0, args.Length - 1);
    82 : return result;
    83:
  }
  84 : 85 : public object EndInvoke(IAsyncResult result) 86 : {
    87 : DispatcherAsyncResultAdapter res =
             result as DispatcherAsyncResultAdapter;
    88 : if (res == null) 89 : throw new InvalidCastException();
    90 : 91
        : while (res.Operation.Status != DispatcherOperationStatus.Completed ||
                 res.Operation.Status == DispatcherOperationStatus.Aborted) 92
        : {
      93 : Thread.Sleep(50);
      94:
    }
    95 : 96 : return res.Operation.Result;
    97:
  }
  98 : 99 : public object Invoke(Delegate method, object[] args) 100 : {
    101 : if (args != null && args.Length > 1) 102 : {
      103 : object[] argsSansFirst = GetArgsAfterFirst(args);
      104 : return m_disp.Invoke(DispatcherPriority.Normal, method, args[0],
                                 argsSansFirst);
      105:
    }
    106 : else 107 : {
      108 : if (args != null) 109 : {
        110 : return m_disp.Invoke(DispatcherPriority.Normal, method, args[0]);
        111:
      }
      112 : else 113 : {
        114 : return m_disp.Invoke(DispatcherPriority.Normal, method);
        115:
      }
      116:
    }
    117:
  }
  118 : 119 : public bool InvokeRequired 120 : {
    121 : get {
      return m_disp.Thread != Thread.CurrentThread;
    }
    122:
  }
  123 : 124 : #endregion 125:
}

All told, I’m still not sure that this will work with delegates with more than two parameters.  I’m still concerned about params argument binding — but what can I do?  Reflection.Emit custom function calls for n parameters?  No thanks.

Microsoft guy who made System.Windows.Threading – for .NET 4.0, how about making Dispatcher implement ISynchronizeInvoke, hm?  Thanks!

This article is part of the GWB Archives. Original Author: gwbarchive

New on Geeks with Blogs

  • We Won The One Award I Actually Care About

    Full Scale made the Inc. 5000 for the fifth year straight, the 12th listing across my three companies. Here is why the one award you cannot buy is worth stopping for.

  • Your Customers Build the Features Now

    I let a tool I liked sit dead for a year rather than build the features I wanted. An MCP server meant I never had to, and your customers can do the same to your product.

  • Get the Size of a Directory in Linux the Easy Way

    du -sh for the quick answer, ncdu for the cleanup, df for the disk itself: every command for checking directory size in Linux, plus why du and df never agree.

  • Vim Search and Replace: The Ultimate Guide

    One :%s command replaces every match in a file before a find dialog would even open. The Vim substitute patterns worth the muscle memory: flags, ranges, capture groups, and multi-file edits.