UI绑定依赖属性的异步处理
Posted
技术标签:
【中文标题】UI绑定依赖属性的异步处理【英文标题】:Async processing on UI bound dependency properties 【发布时间】:2020-05-27 05:59:58 【问题描述】:我正在开发一个 WPF 应用程序,它的业务逻辑由类库(没有 MVVM)处理。业务逻辑的大多数属性都是依赖属性,它允许将数据轻松绑定到 WPF UI。
我有一个显示项目集合(类的依赖属性)的数据网格:ObservableCollection<ItemEntry> EntryCollection
。
目标是为 EntryCollection 中的每个项目异步调用 ItemEntryUpdateAnalyzer.Analyze(ItemTemplate, Company, entry)
静态方法,因为处理需要几秒钟。
我开始做以下事情:
private async void AnalyzeButton_OnClick(object sender, RoutedEventArgs e)
List<Task> tasks = EntryCollection.Select(entry => Task.Run(() => AnalyzeItemEntries())).ToList();
await Task.WhenAll(tasks);
private void AnalyzeItemEntries()
Log.Debug("Begin");
Thread.Sleep(500);
Log.Debug("End");
效果很好,但是添加处理方法会在 ItemTemplate 的依赖属性上引发 System.InvalidOperationException
private void AnalyzeItemEntries(ItemEntry entry)
Log.Debug("Begin");
ItemEntryUpdateAnalyzer.Analyze(ItemTemplate, Company, entry); //InvalidOperationException
Log.Debug("End");
这是因为 Analyze 方法的参数属于主 UI 线程。所以我尝试使用调度程序通过执行以下操作来提供正确的上下文:
private void AnalyzeItemEntries(ItemEntry entry)
Log.Debug("Begin");
/*tried with InvokeAsync as well*/
Dispatcher?.BeginInvoke((Action) (() =>
ItemEntryUpdateAnalyzer.Analyze(ItemTemplate, Company, entry);
));
Log.Debug("End");
但这并没有真正帮助,因为这会锁定主线程。问题是参数通过依赖属性绑定到 UI,普通属性似乎不会抛出异常。
编辑:
我尝试使用 DeepCloner NuGet (https://github.com/force-net/DeepCloner) 将 ItemTemplate 和 ItemEntry 深度复制到局部变量:
private async void AnalyzeButton_OnClick(object sender, RoutedEventArgs e)
Log.Debug($"==== Main thread ID Thread.CurrentThread ===");
ItemTemplate localTemplate = ItemTemplate.DeepClone();
ObservableCollection<ItemEntry> localEntryCollection = EntryCollection.DeepClone();
foreach (ItemEntry entry in localEntryCollection)
await Task.Run(() => AnalyzeItemEntries(localTemplate, entry));
private void AnalyzeItemEntries(ItemTemplate template, ItemEntry entry)
Log.Debug($"Begin entry.ItemCode");
ItemEntryUpdateAnalyzer.Analyze(template, Company, entry);
Log.Debug($"End entry.ItemCode");
我仍然遇到同样的错误。该问题似乎仅与依赖属性有关,因为访问 entry.ItemCode
(标准属性)有效,而访问 entry.Action
无效。
【问题讨论】:
一个异常有一个堆栈跟踪,检查它以获取有关错误发生位置/原因的更多详细信息。 这不是答案,但是当您出于好奇将.ConfigureAwait( false );
添加到WhenAll
时会发生同样的情况吗?
我也试过加.ConfigureAwait(false)
,可惜没用。
@XAMIMAX :UI 元素未在 Analyze
方法中传递。 UI 控件绑定到位于 ItemTemplate
和 ItemEntry
对象中的 Dependency 属性。这算不算糟糕的设计?
当时ItemTemplate的实现和item entry是怎样的?如果这些是您的模型/视图模型,那么您将不需要 DP?通常的 INPC 就足够了。您是否使用这些道具的先前值?这就是为什么你会在模型上使用 DP。或者您是否将 xaml 中的值绑定到您的模型?
【参考方案1】:
依赖属性是一个 UI 级别的对象。后台线程(包括Task.Run
使用的线程)不能直接访问UI 对象。
解决此问题的一种方法是将 UI 属性复制到局部变量中并将这些变量传递给后台线程,如下所示:
private async void AnalyzeButton_OnClick(object sender, RoutedEventArgs e)
var itemTemplate = ItemTemplate;
var company = Company;
List<Task> tasks = EntryCollection.Select(entry => Task.Run(() => AnalyzeItemEntries(itemTemplate, company, entry))).ToList();
await Task.WhenAll(tasks);
根据ItemTemplate
/ Company
/ Entry
的形状,您可能需要将它们制作成“深层”副本。此外,根据AnalyzeItemEntries
的行为,您可能需要更改该方法以返回值,而不是将对象更新为副作用。
【讨论】:
我想过将对象的副本传递给任务,然后返回它们,问题是我无法克隆对象,因为它们不可序列化。 ItemTemplate 有一个手动实现的ToJSON()
,所以我想我可以将它与 JSON 进行转换以获取副本。我更关心来自 API(SAP 的 DIAPI)的 Company 对象
@matthiasboularot:仅在需要时克隆它们(即,如果对象具有线程关联性)。【参考方案2】:
感谢大家的帮助,理想的解决方案是使用常规属性而不是依赖属性,但我不能在我的应用程序中这样做。
我已将DispatcherPriority
设置为ApplicationIdle
。这会恢复对 UI 的一些响应能力。
await Dispatcher?.InvokeAsync(() =>
ItemEntryUpdateAnalyzer.Analyze(ItemTemplate, Company, entry);
,
DispatcherPriority.ApplicationIdle);
【讨论】:
以上是关于UI绑定依赖属性的异步处理的主要内容,如果未能解决你的问题,请参考以下文章