Multi-threaded WPF Application: Dispatcher Invoke.

2020-02-26 00:59发布

问题:

Using .NET 3.5

Hi Guys, I'm making a WPF application for a project and I was just looking a bit of insight regarding the Dispatcher and multithreading. An example of my program:

Application.Current.Dispatcher.Invoke(DispatcherPriority.Background, new Action(
                        () =>_aCollection.Add(new Model(aList[i], aSize[i]))));

Application.Current.Dispatcher.Invoke(DispatcherPriority.Background, new Action(
                        () => _Data.Add(new DataPoint<double, double>(Id, aList[i]))));

 Application.Current.Dispatcher.Invoke(DispatcherPriority.Background, new Action(
                        () => _historical[0].Add(aList[i])));

I understand that WPF does not like when another thread accessing an object other than the one that created it. However, I was thinking there has to surely be a better way than making so many dispatcher invokes, could someone please push me in the right direction at least (if there is a better solution that is).

Cheers, Sparky

回答1:

You can start by being less verbose in your calls, i.e.

Application.Current.Dispatcher.Invoke(() =>_aCollection.Add(new Model(aList[i], aSize[i])));

Another trick that I like to use is to make a shortcut method like this:

public static void UiInvoke(Action a)
{
  Application.Current.Dispatcher.Invoke(a);
}

Then you have even less to do, as in:

UiInvoke(() =>_aCollection.Add(new Model(aList[i], aSize[i])));

Using dispatcher.Invoke() is really just how you get the action back onto the UI thread, which is probably where these objects (_aCollection) were created in the first place. If the items in question don't have direct interaction with the UI thread, then you can create / manipulate them on a different thread, removing the need to use the dispatcher. Of course this approach could become more complicated depending on what you are doing.



回答2:

The easiest way would be to combine all three calls into one:

Application.Current.Dispatcher.Invoke(DispatcherPriority.Background, new Action(
                        () =>
                      {
                          _aCollection.Add(new Model(aList[i], aSize[i]);
                          _Data.Add(new DataPoint<double, double>(Id, aList[i]);
                          _historical[0].Add(aList[i])
                      }));


回答3:

IF you're using .Net 4.0 I would look into using System.Threading.Tasks. This seems like a prime example for continuations.



回答4:

Your problem stems from fact, that ObservableCollection doesn't automaticaly dispatch changes to UI thread. This is different form simple INotifyPropertyChanged, that does it automaticaly. I would recomend creating your own specific ObservableCollection, that implements INotifyCollectionChanged, that automaticaly dispatches changes to UI thread.

You can see example here: SynchronizedObservableCollection and BindableCollection

Old answer/question: Are you using DependencyObject and DependencyProperties for your binding? If yes, then drop it. It was discussed many times and this is one of the bigger reasons why to use INotifyPropertyChanged instead. Only need to use dispatcher is to modify properties of GUI objects themselves and its obvious from your example, that is not what are you doing. And binding itself is run through dispatcher automaticaly.

Also see View Models: POCOs versus DependencyObjects