Running with Code

Like scissors, only more dangerous

  Home  |   Contact  |   Syndication    |   Login
  79 Posts | 0 Stories | 173 Comments | 2 Trackbacks

News



Archives

Post Categories

All Terralever

ASP.NET

Misc

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!

posted on Friday, March 28, 2008 4:56 PM

Feedback

# re: Why doesn't Dispatcher implement ISynchronizeInvoke? 3/29/2008 2:59 PM Bill
Hello Robert, I enjoyed your “rant” and am glad I stumbled across your blog.

# re: Why doesn't Dispatcher implement ISynchronizeInvoke? 3/29/2008 3:03 PM Bill
I never liked the “SynchronizingObject” property pattern – it assumes that ALL subscribers to your event need to be marshaled to a UI thread. (I think it really assumes you have one subscriber). I would rather loop through the event invocation list marshaling only those that need it by examining the Target property of each delegate in the invocation list. Also, in your code example you raise the event (call the subscribers to your event) synchronously when invoke is not required, yet asynchronously when invoke is required (using BeginInvoke() instead of Invoke()).

To extend support to WPF, I would simply check to see if the Target was a UIElement, in which case I would call Invoke() on its dispatcher property if the threads did not match.

The drawback to this approach (and the advantage of having the “SyncrhonizingObject” property) is that it only works when the event receiver (d.Target above) is the object that was created on the UI thread. In many model-view-controller/presenter designs, the object listening for the completion or progress events is not a UI object at all. In this case, the controller can drop some UI object into the SyncrhonizingObject property (the pattern you favor) instead of implementing ISyncrhonizeInvoke, itself. My patterns could not efficiently support a “SynchronizingObject” for both WPF and Windows Forms, unless I just allowed the object to be *either* and ISynchronizeInvoke or a UIElement and simply expose the property as an “object”. Since I don’t like the SynchronizingObject property, anyway, I’m fine with that. Someone using a model-view-controller pattern would probably want to control all this stuff themselves, anyway.

In the end, I am not at all sure that our library objects should be concerning themselves with these things at all! Application code, after all, can deal with these issues themselves in a manner most efficient and appropriate for them. Trouble is - the class library coders tend to be much more advanced than the application coders – they like to write library code like this so the objects “just work” wherever they are dropped in. (at least that is the goal)

Good stuff.





# re: Why doesn't Dispatcher implement ISynchronizeInvoke? 3/29/2008 5:45 PM Rob
Hey Bill,

Thanks for your feedback! The difficulty I see with allowing a UIElement or Visual to be set as the SynchronizingObject property is that then my class library needs to be dependent on the WPF assemblies, which I'm not really a proponent of doing. In at least one of the hardware components, we're working with a vendor who can supply us with .NET 2.0 solutions but not newer. Since ISynchronizeInvoke is in System.ComponentModel (System.dll) it's pretty clear that it's meant for reusable component development. And since most code links against System.dll, I don't have to worry about people having missing dependencies. Why would a class library designed for hardware operation link against WindowsBase.dll anyway?

As to your comments about synchronicity in the invocation of the method - you are indeed correct. I had originally done this synchronized on both ends, but realized that there's not much point to doing it synchronously when I'm needing to use Invoke. The class library isn't designed to handle exceptions that are raised -- if they happen, they happen.

I had considered simply allowing a Dispatcher, but then my event invokers would almost double in size. Yet, my code DOES need to be able to work in both WinForms and WPF environments. What's a developer to do?

At the end of the day, the adapter class is working just fine. I think it's just lame that Microsoft went ahead and completely changed a pattern that was already in place for what - a bit more than five years?

Thanks, Bill!

# re: Why doesn't Dispatcher implement ISynchronizeInvoke? 12/30/2008 10:25 PM Craig
Hi Robert

I'm wanting to use your class - and I will copy and past your code, but just a comment - it's difficult to use code that has line numbers on it. When you copy and paste it, it's a seriously big job to fix it all inside Visual Studio!

Thanks anyway...

# re: Why doesn't Dispatcher implement ISynchronizeInvoke? 1/26/2009 8:50 PM Stephen Cleary
Hi Robert -

WPF uses the Dispatcher, which as you noted, is the WPF equivalent to the Windows Forms ISynchronizeInvoke. What you may not be aware of is that there is already a generic abstraction of this concept: SynchronizationContext (also see AsyncOperation and AsyncOperationManager, which are closely related). Windows Forms exposes a WindowsFormsSynchronizationContext and WPF exposes a DispatcherSynchronizationContext. The idea is that component authors such as yourself can code to SynchronizationContext (or more usually, AsyncOperation which uses SynchronizationContext).

I'm developing a library called Nito.Async that helps address some of the "bridging" issues. For one, it has a GenericSynchronizingObject class that implements ISynchronizeInvoke using SynchronizationContext (so, it can be assigned to SynchronizingObject and works for Windows Forms, WPF, ...).

Nito.Async is free and open source:
http://www.codeplex.com/NitoAsync

-Steve

# re: Why doesn't Dispatcher implement ISynchronizeInvoke? 3/21/2010 4:17 PM Jeff Winn
I just had this same issue with a user of mine asking about a multithreaded WinForms component from the open-source project that I maintain. I ended up doing exactly what you did here to fix the problem. I wrote a ISynchronizeInvoke object that wraps the dispatcher and used it to marshal events back to the user interface. I wish I had found this post before I spent the time digging around in WPF to figure out how thread marshalling happens, could have saved myself some time.

The only difference between what I did, and what you've done here is I also created an IAsyncResult wrapper for the DispatchOperation that results from the BeginInvoke call on the Dispatcher to use the Wait method to wait for the operation to complete rather than pausing the calling thread in a loop until it completes.

Here's a link if you're curious:
http://dotras.codeplex.com/Thread/View.aspx?ThreadId=205863

# re: Why doesn't Dispatcher implement ISynchronizeInvoke? 4/11/2011 5:06 PM Arisha Narsia
Who do you think you are critizing my work. We worked very hard to get many features in WPF, but not everything could make it in. You know in india we are respected and considered with high regard. Suggesting that we may have missed something is rude in our culture. It would be nice if you would consider apolizing or prehaps you would consider removing this post. Thank you.

# re: Why doesn't Dispatcher implement ISynchronizeInvoke? 2/4/2013 6:06 PM Jib Whoosy
Ha ha. You're very funny Arisha. Good article and helped clear up a lot of potential confusion.

Post A Comment
Title:
Name:
Email:
Comment:
Verification: