多线程WPF应用程序:Dispatcher Invoke。有更高效的方法吗?

9

我正在使用.NET 3.5。

我正在为一个项目制作一个WPF应用程序,我想了解一些关于Dispatcher和多线程的见解。这是我的程序示例:

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])));

我知道WPF不喜欢其他线程访问除创建它的对象之外的对象。然而,我认为一定有比频繁使用调度程序调用更好的方法,请问是否有更好的解决方案,如果有,请指点一下。

4个回答

16

你可以通过减少冗长的调用来开始,例如:

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

我喜欢使用的另一个技巧是创建一个类似于以下方式的快捷方法:

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

那么你需要做的事情就更少了,比如:

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

使用dispatcher.Invoke()实际上只是将操作返回到UI线程,这也可能是这些对象(_aCollection)最初创建的位置。如果问题中的项与UI线程没有直接交互,则可以在不同的线程上创建/操作它们,从而无需使用dispatcher。当然,根据您所做的内容,这种方法可能变得更加复杂。


我喜欢这个想法!谢谢。我在一个类中运行了两个线程:一个用于获取数据库数据,另一个用于发出RaisePropertyChanged来更新UI。老实说,我知道这可能不是做事情的最佳方式,因为我对WPF并不是很熟悉,所以目前使用UI线程和BackgroundWorker的想法有点陌生。 - Sparky
1
不,这基本上就是WPF中的做法,所以我认为你的方法很好。我同意UI方面可能会有些麻烦,但我想这是必要的。Coding gorilla也提到了关于continuations的好处,但那些可能会变得非常混乱,所以请注意。祝你好运! - A.R.
我不认为这是“WPF中几乎所有事情都是这样做的”。数据绑定应该自动分派。特别是如果他正在使用MVVM。请查看我的更新答案。 - Euphoric
@Euphoric:虽然对于绑定来说这是非常好的,但在这种情况下显然不适用。此外,你不能对他代码中更复杂的交互做出假设。所以你基本上只能自己进行分派。 - A.R.

13

最简单的方法是将所有三个调用组合成一个:

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])
                      }));

我不知道那是可能的,每天都学到新东西!干杯。 - Sparky
2
@Sparky:使用=>运算符所做的一切都是创建一个lambda表达式,这是匿名委托的一种类型。这些都是编译器最终转换为普通实例(或有时是静态)方法的语法糖。您可以在lambda表达式中执行任何操作(当它用作匿名委托时,就像在此处一样),除了(可能;我没有检查)refout参数。 - Adam Robinson
它对我有效,解决了打印异常:调用线程无法访问此对象,因为另一个线程拥有它。堆栈跟踪:在System.Windows.Threading.Dispatcher.VerifyAccess()。 - digz6666

6

1
如果他已经在消息泵线程之外(那里自动同步将非常有用),任务就无法帮助。 - Adam Robinson
5
当然可以,因为您可以在后台线程上安排“后台”任务,然后使用UI线程上的继续操作“完成”它,例如:Task.ContinueWith(FinishMyStuff, TaskScheduler.FromCurrentSynchronizationContext) - CodingGorilla
1
当然,如果OP有能力将所有当前代码移入任务并将其放置在窗口/控件上,则可以这样做。但是,如果他没有能力重新设计整个线程模型,那么任务是无法帮助的。 - Adam Robinson
3
@AdamRobinson 他在寻求“见解”,并没有提出任何限制,因此我给出了我认为最好的“想法”。如果他选择忽略它,那是他自由的。 - CodingGorilla
大家好,非常感谢你们的回复,不过很遗憾我无法实现它们,因为我正在使用 .Net 3.5 :) 不过,我会研究上述内容以备将来参考!谢谢。 - Sparky

3
你的问题源于 ObservableCollection 不会自动将更改分派到 UI 线程。这与简单的 INotifyPropertyChanged 不同,后者会自动执行此操作。我建议创建自己的特定 ObservableCollection,实现 INotifyCollectionChanged,并自动将更改分派到 UI 线程。
你可以在这里查看示例:SynchronizedObservableCollection 和 BindableCollection

旧回答/问题: 你是否在绑定中使用DependencyObjectDependencyProperties?如果是,则放弃它。这已经讨论了很多次,这也是为什么要使用INotifyPropertyChanged的主要原因之一。只需要使用dispatcher来修改GUI对象本身的属性,从你的示例中可以明显看出这不是你正在做的事情。而绑定本身会自动通过dispatcher运行。

还请参见视图模型:POCO与DependencyObjects


嗨Euphoric,谢谢,但不,我没有使用依赖属性。此外,我正在使用MVVM Light,所以目前我使用“RaisePropertyChanged”而不是“INotifyPropertyChanged”。谢谢。 - Sparky
很奇怪。那么在你的例子中,_aCollection、_Data和_historical是什么?如果它们不是GUI的一部分,那么就没有必要通过Dispatcher来改变它们。而且我认为在使用MVVM时根本没有必要使用Dispatcher。 - Euphoric
嗯...那么在这种情况下,您如何解决未命名线程访问Observable Collection的问题?_Data是Visiblox的图表点,而_aCollection是由ListView绑定的Observable Collection。 - Sparky

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接