如果我想在WPF中反序列化大量UI元素时阻止UI冻结,我有什么解决方案?当我试图在另一个线程中加载它们时,我收到错误抱怨对象属于UI线程。那么,在我加载UI数据时,我有什么选择可以阻止Vista“程序无响应”错误?我可以依赖单线程解决方案,还是我遗漏了一些关于多个UI线程的内容?
答案 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添加代码。或者,您可以修改反序列化器。在任何情况下,请确保您的代码足够频繁地调用
每次获得控制权时,您需要做的就是:
此外,在调用反序列化程序本身时,请确保从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)
答案 5 :(得分:0)
来自OldNewThing博客的建议。
最好是你进入线程路由,拥有一个GUI线程并将你的工作负载产生到另一个线程,当完成报告回到它完成的主GUI线程时。原因是因为您不会遇到GUI界面的线程问题。
一个GUI线程 许多工作线程都在做这项工作。
如果你的任何线程挂起,用户可以直接控制你的应用程序,可以关闭线程而不影响他对应用程序界面的体验。这会让他感到高兴,因为除了他之外,你的用户会感觉自己处于控制状态,不断点击停止按钮并停止搜索。
答案 6 :(得分:0)
尝试freezing你的UIElements。可以在线程之间传递冻结对象,而不会遇到InvalidOperationException,因此您可以反序列化它们&amp;在UI线程上使用它们之前将它们冻结在后台线程上。
或者,考虑将各个反序列化以后台优先级分派回UI线程。这不是最优的,因为UI线程仍然需要完成所有工作来反序列化这些对象,并且通过将它们作为单独的任务分配来增加一些开销,但至少你不会阻止UI - 更高优先级的事件,如输入将能够穿插你的低优先级反序列化工作。