【问题标题】:Prevent UI from freezing without additional threads防止 UI 在没有额外线程的情况下冻结
【发布时间】:2010-12-11 07:41:05
【问题描述】:

如果我想在 WPF 中反序列化大量 UI 元素时防止 UI 冻结,我有哪些解决方案?当我尝试将它们加载到另一个线程中时,我收到错误抱怨对象属于 UI 线程。那么,在加载 UI 数据时,我有哪些选项可以防止 Vista“程序没有响应”错误?我是否可以依赖单线程解决方案,或者我是否遗漏了可能涉及多个 UI 线程的内容?

【问题讨论】:

  • 在 Windows 窗体中,您可以使用 Application.DoEvents()。我不知道它是否适用于 WPF。

标签: wpf multithreading user-interface


【解决方案1】:

如果您只使用单个线程,那么 UI 将在您执行任意数量的处理时冻结。

如果您使用 BackgroundWorker 线程,您将可以更好地控制发生的情况和时间。

要更新 UI,您需要使用后台线程中的 Dispatcher.Invoke 来跨线程边界编组调用。

Dispatcher.Invoke(DispatcherPriority.Background,
                  new Action(() => this.TextBlock.Text = "Processing");

【讨论】:

    【解决方案2】:

    您可以使用 DispatcherFrames 反转控制流,允许在后台的 UI 线程上进行反序列化。

    首先,您需要一种在反序列化期间定期获得控制权的方法。无论您使用什么反序列化器,它都必须在您的对象上调用属性集,因此您通常可以将代码添加到属性设置器中。或者,您可以修改反序列化器。无论如何,请确保您的代码被足够频繁地调用

    每次您获得控制权时,您需要做的就是:

    1. 创建一个 DispatcherFrame
    2. 使用在框架上设置 Continue=false 的 BeginInvoke 将事件排队到调度程序
    3. 使用 PushFrame 启动在 Dispatcher 上运行的帧

    此外,在调用反序列化器本身时,请确保您从 Dispatcher.BeginInvoke 执行此操作,或者您的调用代码不持有任何锁等。

    它的外观如下:

      public partial class MyWindow
      {
        SomeDeserializer _deserializer = new SomeDeserializer();
    
        byte[] _sourceData;
        object _deserializedObject;
    
        ...
    
        void LoadButton_Click(...)
        {
          Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() =>
          {
            _deserializedObject = _deserializer.DeserializeObject(_sourceData);
          }));
        }
      }
    
      public class OneOfTheObjectsBeingDeserializedFrequently
      {
        ...
    
        public string SomePropertyThatIsFrequentlySet
        {
          get { ... }
          set { ...; BackgroundThreadingSolution.DoEvents(); }
        }
      }
    
      public class BackgroundThreadingSolution
      {
        [ThreadLocal]
        static DateTime _nextDispatchTime;
    
        public static void DoEvents()
        {
          // Limit dispatcher queue running to once every 200ms
          var now = DateTime.Now;
          if(now < _nextDispatchTime) return;
          _nextDispatchTime = now.AddMilliseconds(200);
    
          // Run the dispatcher for everything over background priority
          var frame = new DispatcherFrame();
          Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() =>
          {
            frame.Continue = false;
          }));
          Dispatcher.PushFrame(frame);
        }
      }
    

    在 DoEvents() 中检查 DateTime.Now 实际上并不是这种技术起作用所必需的,但如果在反序列化期间非常频繁地设置 SomeProperty 会提高性能。

    编辑:在我写完这篇文章后,我意识到有一种更简单的方法可以实现 DoEvents 方法。而不是使用 DispatcherFrame,只需使用 Dispatcher.Invoke 和一个空操作:

        public static void DoEvents()
        {
          // Limit dispatcher queue running to once every 200ms
          var now = DateTime.Now;
          if(now < _nextDispatchTime) return;
          _nextDispatchTime = now.AddMilliseconds(200);
    
          // Run the dispatcher for everything over background priority
          Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Background, new Action(() => {}));
        }
    

    【讨论】:

      【解决方案3】:

      Here is a wonderful blog posting from Dwane Need 讨论了在多个线程中处理 UI 元素的所有可用选项。

      您确实没有提供足够的细节来给出一个好的处方。例如,您为什么要自己创建 UI 元素而不是使用数据绑定?您可能有充分的理由,但如果没有更多细节,就很难给出好的建议。作为另一个有用的细节示例,您是希望为每条数据构建复杂的深度嵌套控制层次结构,还是只需要绘制一个简单的形状?

      【讨论】:

        【解决方案4】:

        我的面板也有类似的问题,它正在移动它的项目。 UI 冻结了,因为我在优先加载时使用了 DispatcherTimer。一旦我将其更改为 DispatcherPriority.Input,问题就消失了。

        【讨论】:

          【解决方案5】:

          您仍然可以在单独的线程中进行长时间处理,但完成后您必须通过调用 Dispatcher.BeginInvoke(your_UI_action_here) 与 UI 线程同步

          【讨论】:

            【解决方案6】:

            来自 OldNewThing 博客的建议。

            最好是走线程化路线,拥有一个 GUI 线程并将工作负载转移到另一个线程,该线程在完成后向主 GUI 线程报告其已完成。这样做的原因是您不会遇到 GUI 界面的线程问题。

            所以只有一个 GUI 线程 许多工作线程来完成这项工作。

            如果您的任何线程确实挂起,那么直接控制您的应用程序的用户可以关闭线程,而不会影响他对应用程序界面的体验。这会让他很高兴,因为除了他之外,您的用户会感到自己处于控制之中,他会不断点击停止按钮并且它不会停止搜索。

            【讨论】:

              【解决方案7】:

              试试 freezing 你的 UIElements。冻结的对象可以在线程之间传递而不会遇到 InvalidOperationException,因此您可以在将它们反序列化并在后台线程上冻结它们,然后再在您的 UI 线程上使用它们。

              或者,考虑以后台优先级将各个反序列化分派回 UI 线程。这不是最佳的,因为 UI 线程仍然必须完成所有工作来反序列化这些对象,并且通过将它们作为单独的任务分派会增加一些开销,但至少您不会阻塞 UI - 更高优先级的事件,如输入将能够穿插在你的低优先级反序列化工作中。

              【讨论】:

              • 这仅适用于可冻结元素,UI 元素不可冻结。因此,如果您只是在谈论创建几何体、画笔等,这是可行的,但如果您是在谈论构建 UI 元素(即视觉类和继承链的上游),则不会。
              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2012-02-07
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2019-10-02
              相关资源
              最近更新 更多