Истинное разделение кода и представления при использовании Dispatcher - PullRequest
10 голосов
/ 10 февраля 2011

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

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

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

Ответы [ 9 ]

3 голосов
/ 10 февраля 2011

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

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

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

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

2 голосов
/ 10 февраля 2011

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

2 голосов
/ 10 февраля 2011

Вариант 1

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

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

Опция 2

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

1 голос
/ 10 февраля 2011

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

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

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

0 голосов
/ 10 февраля 2011

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

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

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

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

0 голосов
/ 10 февраля 2011

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

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

РЕДАКТИРОВАТЬ: я украл его из сообщения stackoverflow, но забыл, из какого

0 голосов
/ 10 февраля 2011

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

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

0 голосов
/ 10 февраля 2011

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

0 голосов
/ 10 февраля 2011

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

С этой записи :

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

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