我正在使用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
则无效。
依赖关系属性是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
的行为,可能需要更改该方法以返回值,而不是作为副作用更新对象。
感谢大家的帮助,理想的解决方案是使用常规属性而不是依赖属性,但是我不能在我的应用程序中这样做。
我已经将DispatcherPriority
设置为ApplicationIdle
。这会给UI带来一些响应。
await Dispatcher?.InvokeAsync(() =>
{
ItemEntryUpdateAnalyzer.Analyze(ItemTemplate, Company, entry);
},
DispatcherPriority.ApplicationIdle);