【问题标题】:WPF update binding in a background thread后台线程中的 WPF 更新绑定
【发布时间】:2010-03-24 05:13:38
【问题描述】:

我有一个控件,它的数据绑定到标准ObservableCollection,我有一个后台任务调用服务来获取更多数据。

然后,我想在我的控制下更新我的支持数据,同时显示“请稍候”对话框,但是当我将新项目添加到集合中时,UI 线程在重新绑定并更新我的控制。

我能否解决这个问题,让我的动画和内容继续在“请稍候”对话框中运行?

或者至少给用户一个没有被锁定的“外观”?

【问题讨论】:

    标签: wpf data-binding multithreading


    【解决方案1】:

    如果我理解正确,您已经使用 BackgroundWorker 来检索数据,并且简单地将这些数据分配给 ObservableCollection 会锁定 UI。

    避免锁定 UI 的一种方法是通过对多个调度程序方法进行排队,以较小的块将数据分配给 ObservableCollection。在每个方法调用之间,可以处理 UI 事件。

    下面会一次添加一个项目,这有点极端,但它说明了这个概念。

    void UpdateItems()
    {
        //retrievedItems is the data you received from the service
        foreach(object item in retrievedItems)
            Dispatcher.BeginInvoke(DispatcherPriority.Background, new ParameterizedThreadStart(AddItem), item);    
    }
    
    void AddItem(object item)
    {
        observableCollection.Add(item);
    }
    

    【讨论】:

    • 那么通过将它们添加到后台线程中的项目集合将更新 UI 线程上的控件?它是如何工作的?
    • 不,您将它们添加到调度程序线程中,但优先级较低且块较小,因此 UI 保持响应
    • 正是我想要的。成就了我的一天。谢谢。
    【解决方案2】:

    ObservableCollection 将引发 CollectionChanged 事件,这些事件将强制 UI 重新绑定数据、测量、排列和重绘。如果您有很多更新,这可能需要很长时间。

    通过将作业拆分为小包,可以让用户认为 UI 是有生命的。使用 UI 线程中的 Dispatcher(任何控件都引用它)来安排包含 10-100 个项目的集合更新操作(通过实验确定数量,这些只是为了支持这个想法)。

    您的后台代码可能如下所示:

    void WorkInBackground()
    {
        var results = new List<object>();
    
        //get results...
    
        // feed UI in packages no more than 100 items
        while (results.Count > 0)
        {
            Application.Current.MainWindow.Dispatcher.BeginInvoke(
                new Action<List<object>>(FeedUI),
                DispatcherPriority.Background,
                results.GetRange(0, Math.Min(results.Count, 100)));
            results.RemoveRange(0, Math.Min(results.Count, 100));
        }
    }
    void FeedUI(List<object> items)
    {
        // items.Count must be small enough to keep UI looks alive
        foreach (var item in items)
        {
            MyCollection.Add(item);
        }
    }
    

    【讨论】:

      【解决方案3】:

      我有一个运行工作线程并将事件发送回应用程序的 DLL - 在 Windows 窗体上完美运行,切换到 WPF 并且一切都停止工作。我已经把头撞在砖墙上了 4 个小时,试图让它发挥作用。但由于微软的 UI 线程安全编组 EnableCollectionSynchronization,我最终得到的解决方案提供了一个非常干净的实现来解决这个问题。

      此集合扩展了 ObservableCollection 并实现了 EnableCollectionSynchronization,使这些对象可在 WPF 和后台工作人员之间使用。

      编辑Microsoft's docs 说如下,所以我假设对象上下文共享无关紧要。

      上下文参数是一个任意对象,您可以在启用集合同步时使用它来获取已知信息。上下文可以是null

      ThreadSafeCollection.cs

      using System.Collections.ObjectModel;
      using System.Windows.Data;
      
      namespace NSYourApplication
      {
          /// <summary>
          /// This ObservableCollection is thread safe
          /// You can update it from any thread and the changes will be safely
          /// marshalled to the UI Thread WPF bindings
          /// Thanks Microsoft!
          /// </summary>
          /// <typeparam name="T">Whatever type of collection you want!</typeparam>
          public class ThreadSafeCollection<T> : ObservableCollection<T>
          {
              private static object __threadsafelock = new object();
      
              public ThreadSafeCollection()
              {
                  BindingOperations.EnableCollectionSynchronization(this, __threadsafelock);
              }
          }
      }
      

      示例 WindowViewModel WindowViewModel.cs

      namespace NSYourApplication
      {
          /// <summary>
          /// Example View 
          /// BaseModelView implements "PropertyChanged" to update WPF automagically
          /// </summary>
          class TestViewModel : BaseModelView
          {
              public ThreadSafeCollection<string> StringCollection { get; set; }
      
              /// <summary>
              /// background thread implemented elsewhere...
              /// but it calls this method eventually ;)
              /// Depending on the complexity you might want to implement
              /// [MethodImpl(MethodImplOptions.Synchronized)]
              /// to Synchronize multiple threads to prevent chase-conditions,deadlocks etc
              /// </summary>
              public void NonUIThreadMethod()
              {
                  // No dispatchers or invokes required here!
                  StringCollection.Add("Some Text from a background worker");
              }
      
              /// <summary>
              /// Somewhere in the UIThread code it'll call this method
              /// </summary>
              public void UIThreadMethod()
              {
                  StringCollection.Add("This text come from UI Thread");
              }
      
              /// <summary>
              /// Constructor, creates a thread-safe collection
              /// </summary>
              public TestViewModel()
              {
                  StringCollection = new ThreadSafeCollection<string>();
              }
          }
      }
      

      在 xaml 窗口/控件的列表框中使用 MainWindow.xaml

          <ListBox x:Name="wpfStringCollection" ItemsSource="{Binding StringCollection,Mode=OneWay}">
      
          </ListBox>
      

      【讨论】:

      • 嗯,考虑一下;这个单一的静态 __threadsafelock 将由所有集合共享;这可能会导致问题;)我将不得不回到这个... =]
      【解决方案4】:

      使用 BackgroundWorker 来完成这项任务。更新 DoWork 方法中的 obsrvablecollection

      【讨论】:

      • 如何更新 UI 线程?也许我不明白这一点,但我的所有控件和它们背后的渲染都不是在 UI 线程中,所以在后台线程中更新集合只会导致 UI 线程进行正常更新?
      【解决方案5】:

      使用这个:

      Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Render, new Action(UpdateData), value); private void UpdateData(int value) { BindingSourceProperty = value; }

      【讨论】:

      • 可以说您有一个 WPF UI 表单,其中 ProgressBar 控件为 其中 PercentValue 是在附加到表单的 DataContext 中定义的属性.假设您在某个其他线程上获取回调数据以指示操作进度,并且您希望将其显示在 UI 上。如果将该值直接设置为依赖源属性“PercentValue”,则只有在后台线程操作完成时才会更新 ui。因此,为了获得百分比值的实时进度,请将此值设置为如上
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-12-16
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多