Search
Close this search box.

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: Running with Code

Related Posts