【问题标题】:Async processing on UI bound dependency propertiesUI绑定依赖属性的异步处理
【发布时间】: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 控件绑定到位于 ItemTemplateItemEntry 对象中的 Dependency 属性。这算不算糟糕的设计?
  • 当时ItemTemplate的实现和item entry是怎样的?如果这些是您的模型/视图模型,那么您将不需要 DP?通常的 INPC 就足够了。您是否使用这些道具的先前值?这就是为什么你会在模型上使用 DP。或者您是否将 xaml 中的值绑定到您的模型?

标签: wpf asynchronous task dependency-properties dispatcher


【解决方案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);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-01-22
    • 2012-11-08
    • 2012-08-01
    • 2017-12-24
    • 1970-01-01
    • 2014-08-31
    • 2012-09-28
    相关资源
    最近更新 更多