Running with Code Like with scissors, only more dangerous

28Mar/080

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!

Tagged as: , Leave a comment
Comments (0) Trackbacks (0)

No comments yet.


Leave a comment

ERROR: si-captcha.php plugin says GD image support not detected in PHP!

Contact your web host and ask them why GD image support is not enabled for PHP.

ERROR: si-captcha.php plugin says imagepng function not detected in PHP!

Contact your web host and ask them why imagepng function is not enabled for PHP.

No trackbacks yet.