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!