Asyn c обработка свойств зависимостей, связанных с пользовательским интерфейсом - PullRequest
1 голос
/ 11 февраля 2020

Я работаю над приложением WPF, чьи бизнес-логики c обрабатываются библиотекой классов (без MVVM). Большинство свойств бизнес-логики c являются свойствами зависимостей, что позволяет легко связывать данные с пользовательским интерфейсом WPF.

У меня есть сетка данных, которая отображает коллекцию элементов (свойство зависимости класса): ObservableCollection<ItemEntry> EntryCollection.

Цель состоит в том, чтобы асинхронно вызывать метод ItemEntryUpdateAnalyzer.Analyze(ItemTemplate, Company, entry) stati c для каждый элемент в EntryCollection, так как обработка занимает несколько секунд.

Я начал с выполнения следующих действий:

    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");
    }

Это работало нормально, но добавление метода обработки вызывает исключение System.InvalidOperationException для свойства зависимостей ItemTemplate

    private void AnalyzeItemEntries(ItemEntry entry)
    {
        Log.Debug("Begin");
        ItemEntryUpdateAnalyzer.Analyze(ItemTemplate, Company, entry); //InvalidOperationException
        Log.Debug("End");
    }

Это связано с тем, что аргументы метода Analyze принадлежат основному потоку пользовательского интерфейса. Поэтому я попытался использовать диспетчер, чтобы получить правильный контекст, выполнив следующее:

    private void AnalyzeItemEntries(ItemEntry entry)
    {
        Log.Debug("Begin");
        /*tried with InvokeAsync as well*/
        Dispatcher?.BeginInvoke((Action) (() =>
        {
            ItemEntryUpdateAnalyzer.Analyze(ItemTemplate, Company, entry);
        }));
        Log.Debug("End");
    }

Но это не очень помогает, поскольку это блокирует основной поток. Проблема в том, что аргументы связывают пользовательский интерфейс с помощью свойств зависимости, обычные свойства, похоже, не выдают исключение.

РЕДАКТИРОВАТЬ:

Я пытался глубоко скопировать ItemTemplate и ItemEntry в локальные переменные, используя DeepCloner NuGet (https://github.com/force-net/DeepCloner):

    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 - нет.

Ответы [ 2 ]

0 голосов
/ 12 февраля 2020

Спасибо всем за вашу помощь. Идеальным решением было бы использование обычных свойств вместо свойств зависимостей, но я не могу сделать это в своем приложении.

Я установил DispatcherPriority в ApplicationIdle. это возвращает некоторую отзывчивость к пользовательскому интерфейсу.

            await Dispatcher?.InvokeAsync(() =>
            {
                ItemEntryUpdateAnalyzer.Analyze(ItemTemplate, Company, entry);

            },
            DispatcherPriority.ApplicationIdle);
0 голосов
/ 12 февраля 2020

Свойства зависимостей являются объектами уровня пользовательского интерфейса. Фоновые потоки (включая те, которые используются Task.Run) не могут напрямую обращаться к объектам пользовательского интерфейса.

Одним из способов является копирование свойств пользовательского интерфейса в локальные переменные и передача этих переменных в фоновый поток, что-то вроде этого:

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 вам может потребоваться изменить этот метод, чтобы он возвращал значения, а не обновлять объекты как побочные эффекты.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...