【发布时间】:2018-09-17 03:24:02
【问题描述】:
如果我们使用带有 async 和 await 关键字的基于任务的异步模式,则 UI 锁定没有问题。例如,我们可以花 10 秒从服务器加载数据,同时愉快地显示一个等待指示器。但是,当在 UI 线程上运行复杂任务时,线程会锁定,等待指示器中的动画会冻结。
当然,一种策略可能是完全避免使用沉重的 UI,但在这种情况下,这并不是一个真正的选择。我正在将数百个 TreeViewItems 加载到屏幕上。这显然会导致 UI 锁定。
我曾尝试像这样将工作放在控件的 Dispatcher 上,但没有帮助:
var action = new Action(() =>
{
SchemaTree.Items.Clear();
foreach (var assemblyStructure in assemblyStructures)
{
var assemblyNode = CreateTreeNode(SchemaTree.Items, assemblyStructure.Assembly.Name.Replace("Adapt.Model.", string.Empty), assemblyStructure.Assembly, "/Icons/Schema.PNG");
foreach (var theNameSpace in assemblyStructure.Namespaces)
{
var namespaceNode = CreateTreeNode(assemblyNode.Items, theNameSpace.TheNamespace.Name, theNameSpace.TheNamespace, "/Icons/Namespace.PNG");
foreach (var classInfo in theNameSpace.Classes)
{
CreateClassInfoNode(theNameSpace, classInfo, namespaceNode);
}
}
}
});
await SchemaTree.Dispatcher.BeginInvoke(action, DispatcherPriority.Background, null);
如果我使用 Task.Run 将工作转移到任务上,它实际上会阻止 UI 锁定,但很明显,我遇到了跨线程冲突。
var action = new Action(async () =>
{
await Task.Run(() =>
{
SchemaTree.Items.Clear();
foreach (var assemblyStructure in assemblyStructures)
{
var assemblyNode = CreateTreeNode(SchemaTree.Items, assemblyStructure.Assembly.Name.Replace("Adapt.Model.", string.Empty), assemblyStructure.Assembly, "/Icons/Schema.PNG");
foreach (var theNameSpace in assemblyStructure.Namespaces)
{
var namespaceNode = CreateTreeNode(assemblyNode.Items, theNameSpace.TheNamespace.Name, theNameSpace.TheNamespace, "/Icons/Namespace.PNG");
foreach (var classInfo in theNameSpace.Classes)
{
CreateClassInfoNode(theNameSpace, classInfo, namespaceNode);
}
}
}
});
});
await SchemaTree.Dispatcher.BeginInvoke(action, DispatcherPriority.Background, null);
关于如何告诉 UI 降低这项工作的优先级以便等待指示器有机会进行动画处理并且整个 UI 不会锁定的任何想法?
【问题讨论】:
-
你可以尝试将动作分解成小动作,每个动作只做一个组装结构。然后每次等待一个动作;你有很多 Dispatcher.BeginInvoke 调用,而不是一个巨大的动作,但 UI 可以有机会运行 animator。
-
是的 - 你是对的。我已经在我的代码的另一个地方做到了这一点,我可以看到不同之处。问题是这会导致另一个问题。在 TreeView 的情况下,我可以从字面上看到树节点在我眼前一一填充,但我无法判断所有节点何时都已渲染。
-
另外,这不能用 ListBox 来完成,因为我将 ListBoxItems 的创建交给了控件本身来做。
-
"我正在将数百个 TreeViewItems 加载到屏幕上" - 您应该完全避免这种情况。使用虚拟化并仅为当前可见的元素创建 UI。对于不可见的,只异步加载他们的数据,没有跨线程访问问题。使用数据模板。
-
请将其发布为答案。听起来像 WinForms 中的 Application.DoEvents,我认为它是出于相同目的而提供的?会检查文档并做一些实验,我之前没有使用过。
标签: wpf multithreading locking dispatcher unresponsive-progressbar