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!