在没有其他线程的情况下防止UI冻结

时间:2009-10-29 16:17:59

标签: wpf multithreading user-interface

如果我想在WPF中反序列化大量UI元素时阻止UI冻结,我有什么解决方案?当我试图在另一个线程中加载它们时,我收到错误抱怨对象属于UI线程。那么,在我加载UI数据时,我有什么选择可以阻止Vista“程序无响应”错误?我可以依赖单线程解决方案,还是我遗漏了一些关于多个UI线程的内容?

7 个答案:

答案 0 :(得分:11)

如果您只使用单个线程,则在您进行任何处理时,UI将冻结。

如果你使用BackgroundWorker线程,你可以更好地控制发生的事情&当

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

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

答案 1 :(得分:2)

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

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

答案 2 :(得分:2)

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

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

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

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

    此外,在调用反序列化程序本身时,请确保从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 :(得分:1)

我的面板有一个类似的问题,就是移动它的物品。用户界面很冷,因为我在优先级已加载时使用DispatcherTimer。一旦我将其更改为DispatcherPriority.Input。

,问题就消失了

答案 4 :(得分:0)

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

与UI线程同步

答案 5 :(得分:0)

来自OldNewThing博客的建议。

最好是你进入线程路由,拥有一个GUI线程并将你的工作负载产生到另一个线程,当完成报告回到它完成的主GUI线程时。原因是因为您不会遇到GUI界面的线程问题。

一个GUI线程 许多工作线程都在做这项工作。

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

答案 6 :(得分:0)

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

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