Истинное разделение кода и презентации при использовании Диспетчера

В моем WPF попытаться отделить логику классов от любых связанных с интерфейсом данных и предоставить свойства ObservableCollection для привязки.

Проблема в том, что когда я обращаюсь к этим связанным OC из других потоков, я должен сделать это через диспетчер. В результате я должен добавить много вызовов Dispatcher.Invoke(), скрытых внутри моих классов, всякий раз, когда один из методов пытается обновить OC.

Как я могу сделать это более чистым и разделенным образом, чтобы вызовы диспетчера были отвлечены от моих методов?

Ответ 1

Вариант 1

Я думаю, вы должны изучить лучшее разделение вашего кода с использованием шаблона MVVM, если вы не знакомы с ним, я настоятельно рекомендую увидеть следуя видео, поскольку он точно определяет, что вы ищете.

В частности, в вашем случае вы должны иметь класс model с регулярной коллекцией (например, List), на которой вы выполняете всю работу в потоках. Ваш ViewModel должен содержать ObservableCollections и подключаться свободно с коллекциями, существующими в модели, например, вы можете подписаться на мероприятие из вашего ViewModel на определенную логику обновления в вашей модели. Вам будет необходимо использовать диспетчер для обновления OC, но вам нужно будет сделать это только один раз.

Вариант 2

Вместо этого вы можете просто использовать описанное решение здесь. В принципе, он создал новый производный класс от OC, который позволяет вам автоматически отправлять изменения из кода, не требуя обновления самого диспетчера.

Ответ 2

У меня нет серебряной пули. Но если вы уверены и готовы взять на себя ответственность за неявное делегирование пользовательского интерфейса, вы всегда можете наследовать от ObservableCollection, переопределять методы и отправлять все запросы в пользовательский интерфейс.

Но следующий код меня пугает:

// somewhere in thread pool:
for(int i = 0; i < 1000; i++)
{
   _dispatcherAwareCollection.Add(i);
}

Кажется невиновным, но под капотом он блокирует вызов потока 1000 раз. Альтернативами могут быть ваши конкретные методы BulkXXX(), которые будут откладывать уведомление до тех пор, пока все элементы не будут обработаны. Это решение также не является идеальным, так как вы хотели абстрагироваться, что позволило бы вам легко обменивать коллекции, но методы BulkXXX() очень специфичны для новой коллекции.

Ответ 3

Общий подход заключается в том, чтобы иметь свойство Dispatcher на вашей модели представления (возможно, в базовом классе для всех моделей представлений), которое может быть введено снаружи. Это нормально для модели представления, потому что модель представления СЛЕДУЕТ осознавать концепции пользовательского интерфейса, но не должна знать конкретного вида (макет, элементы управления и т.д.) И, конечно же, не должна иметь ссылку на представление.

Что вы можете сделать, так это упростить отправку кода в поток Dispatcher, создав помощника или службу, которая отвлечет диспетчера. Например, вы можете создать помощник, подобный этому:

public class AsyncHelper
{
    public static void EnsureUIThread(Action action) 
    {
        if (Application.Current != null && !Application.Current.Dispatcher.CheckAccess()) 
        {
            Application.Current.Dispatcher.BeginInvoke(action, DispatcherPriority.Background);
        }
        else 
        {
            action();
        }
    }
}

И всякий раз, когда вам нужно обновить наблюдаемую коллекцию, вы завершаете код в этом вспомогательном методе:

AsyncHelper.EnsureUIThread(() =>
{
    // Update you observable collections here
});

ИЛИ, вы можете пойти дальше и использовать AOP (например PostSharp), чтобы указать декларативно (используя атрибуты), что метод должен быть выполнен в Пользовательский интерфейс.

И, наконец, обратите внимание, что вам нужно отправлять только обновления коллекции в поток пользовательского интерфейса. Обычные свойства можно безопасно обновлять из фонового потока. Обновления будут автоматически отправляться в поток пользовательского интерфейса с помощью механизма привязки. Вероятно, в будущих версиях обновления WPF к коллекции из фонового потока также будут поддерживаться.

Ответ 4

Ну, вы могли бы написать себе AsyncObservableCollection, если знаете, как писать это потоки. Затем вы можете инкапсулировать в него вызовы Dispatcher. Проблема в том, что вы не будете использовать стандартный ObservableCollection, поставляемый в .Net-Framework. Это увеличит риск ошибок в вашем приложении.

Другой вариант - реализовать WrapperClass, который содержит и предоставляет ObservableCollection для привязки и имеет методы для изменения коллекции.


public class WrapperClass<T>
{
   public ObservableCollection<T> Collection {get; set;}

   public void Add(T item) 
   { 
      //do your dispatcher magic here 
   }
   ...
}

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

Ответ 5

Я боюсь, что вам придется ждать следующей версии wpf

Из этот пост:

Несколько самородков, которые мы можем ожидать в следующей версии WPF, включают в себя:

  • Хостинг содержимого Silverlight с помощью нового элемента SilverlightHost без проблем с воздушным пространством (невозможность перекрывать содержимое WPF поверх собственного содержимого Windows hWnd)
  • Лучшее управление воздушным пространством с размещенным на нем контентом на основе hWnd, таким как WebBrowser, HwndHost и WindowsFormsHost
  • Включение привязки и изменения уведомлений для коллекций, созданных в фоновом потоке
  • Улучшенная интеграция с виртуализацией пользовательского интерфейса
  • Интеграция управления лентой
  • И еще

Ответ 6

Используйте SynchronizationContext вместо Диспетчера. SynchronizationContext - общая функция синхронизации потоков в .NET, между тем диспетчер намеренно разработан для WPF.

Ответ 7

Вероятно, вы хотите использовать что-то вроде MTObservableCollection. Я использовал это в проекте, и он работал фантастически. В принципе, он выполняет всю диспетчерскую работу для вас, когда событие с изменением коллекции увеличивается, анализируя поток, от которого был назначен обработчик, и отправляя соответственно.

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

Ответ 8

У меня есть расширение для этого:

 public static class DispatcherInvoker
 {       

    public static void AddOnUI<T>(this ICollection<T> collection, T item)
    {
        Action<T> addMethod = collection.Add;
        Application.Current.Dispatcher.BeginInvoke(addMethod, item);
    }
 }

EDIT: Я украл его из stackoverflow-сообщения, но забыл, из которого один

Ответ 9

Я думаю, что вам нужно много сочетать, если вам нужно подумать о потоковом потоке в вашем слое модели.

Что вы должны сделать, так это не подключать вашу модель напрямую к графическому интерфейсу. Как говорили другие, используйте слой между (MVVM).

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

Вкратце: Продолжайте использовать ObeservableCollection в вашем слое модели, если хотите, но не используйте его непосредственно в привязке GUI. Позвольте другому уровню получать уведомления и управлять обновлением графического интерфейса.