【问题标题】:Multi-threaded WPF Application: Dispatcher Invoke. A more efficient way?多线程 WPF 应用程序:调度程序调用。更有效的方法?
【发布时间】:2011-03-28 19:30:22
【问题描述】:

我正在使用 .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 不喜欢另一个线程访问创建它的对象以外的对象。但是,我认为肯定有比进行如此多的调度程序调用更好的方法,是否有人至少可以将我推向正确的方向(如果有更好的解决方案)。

【问题讨论】:

    标签: c# wpf multithreading dispatcher


    【解决方案1】:

    您可以从减少通话中的冗长开始,即

    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 线程没有直接交互,那么您可以在不同的线程上创建/操作它们,从而无需使用调度程序。当然,这种方法可能会变得更加复杂,具体取决于您在做什么。

    【讨论】:

    • 我喜欢这个主意!谢谢。我在一个类中运行了 2 个线程:1 个用于获取数据库的数据,另一个用于发出 RaisePropertyChanged 以更新 UI。我知道这可能不是最好的做事方式,老实说,我不是 WPF 专家,所以 UI 线程和 backgroundWorkers 的想法对我来说有点陌生
    • 不,这几乎就是 WPF 中的工作方式,所以我会说你的方法很好。我同意做 UI 的东西可能会很痛苦,但我想这是必要的邪恶。编码大猩猩也很好地说明了延续,但那些可能会进入超级倾斜领域,因此请注意。祝你好运!
    • 我不认为这是“WPF 中的工作方式”。数据绑定应该自动进行调度。特别是如果他正在使用 MVVM。请参阅我的更新答案。
    • @Euphoric:这对绑定来说很好,但显然不是这里的情况。此外,您不能对他的代码更复杂的交互做出假设。因此,您几乎可以自己进行调度。
    【解决方案2】:

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

    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:你对=&gt; 操作符所做的只是创建一个 lambda 表达式,它是一种匿名委托。这些都是编译器最终变成普通实例(或有时是静态)方法的语法糖。您可以在 lambda 表达式中执行任何您可以在普通函数中执行的操作(当它用作匿名委托时,就像这里一样),除了(可能;我还没有检查)refout 参数.
    • 它对我有用,摆脱了打印异常:调用线程无法访问此对象,因为另一个线程拥有它。堆栈跟踪:在 System.Windows.Threading.Dispatcher.VerifyAccess()跨度>
    【解决方案3】:

    如果您使用的是 .Net 4.0,我会考虑使用 System.Threading.Tasks。这似乎是 continuations 的一个典型例子。

    【讨论】:

    • 如果他已经在消息泵线程之外(这是自动同步派上用场的地方),任务将无济于事
    • 当然可以,因为您可以在后台线程上安排“后台”任务,然后在 UI 线程上继续“完成”它:Task.ContinueWith(FinishMyStuff, TaskScheduler.FromCurrentSynchronizationContext)
    • 当然,如果 OP 能够将所有当前代码移动到任务中并将其全部放在窗口/控件上。但是,如果他无法重新设计整个线程模型,那么 Tasks 将无济于事。
    • @AdamRobinson 好吧,他是在寻求“洞察力”,但他没有提供任何限制,所以我提出了我认为最好的“想法”。如果他愿意,他可以随意忽略它。
    • 嗨,伙计们,非常感谢您的回复,不幸的是我无法实现它们,因为我正在使用.Net 3.5 :) 尽管如此,我会研究上述内容以供将来参考!干杯
    【解决方案4】:

    您的问题源于事实,ObservableCollection 不会自动将更改分派到 UI 线程。这是与简单的 INotifyPropertyChanged 不同的形式,它会自动完成。我建议创建您自己的特定 ObservableCollection,实现 INotifyCollectionChanged,自动将更改分派到 UI 线程。

    您可以在此处查看示例:SynchronizedObservableCollection and BindableCollection

    旧答案/问题: 您是否使用DependencyObjectDependencyProperties 进行绑定?如果是,那就放弃吧。它被讨论了很多次,这是使用INotifyPropertyChanged 的更大原因之一。只需要使用调度程序就是修改 GUI 对象本身的属性,从您的示例中可以明显看出,这不是您在做什么。绑定本身是通过调度程序自动运行的。

    另见View Models: POCOs versus DependencyObjects

    【讨论】:

    • 嗨欣快,干杯,但不,我没有使用依赖属性。此外,我正在使用 MVVM 灯,因此目前我使用“RaisePropertyChanged”而不是“INotifyPropertyChanged”。谢谢
    • 这很奇怪。那么在您的示例中,_aCollection、_Data 和 _historical 是什么?如果它们不是 GUI 的一部分,则无需通过 Dispatcher 更改它们。而且我认为在使用 MVVM 时不需要使用 Dispatcher。
    • 嗯...在这种情况下,您将如何解决未命名线程访问可观察集合的问题? _Data 是 Visiblox 的图表点,_aCollection 是由列表视图绑定的可观察集合
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多