Подтвердить что ты не робот

Неправильно ли использовать Диспетчер в моей модели ViewModel?

Я конвертирую чат-парсер для игры, в которую играю, я написал в С# winforms для wpf, главным образом, чтобы получить лучший дескриптор MVVM и wpf. Ниже приведен пример того, как настроен мой проект

Вид: На данный момент это просто простой ListBox с ItemSource, связанный с моей наблюдаемой коллекцией наблюдаемых чатов

Model: У меня есть несколько символов, которые могут быть зарегистрированы в одно время, и каждый символ имеет класс чата. В чат-классе начинается фоновый рабочий, который захватывает и следующую строку чата из игры и запускает событие под названием IncomingChat с этой строкой.

public event Action<Game.ChatLine> IncomingChat;

Я использую фона рабочего для того, чтобы запустить событие в моем workworked workworked, потому что, когда я использовал таймер, я продолжал получать проблему с потоками. Сначала я исправил это, изменив мой Timer на DispatchTimer, но мне не показалось, что у меня есть DispatchTimer в моей модели.

ViewModel: Поскольку у меня есть несколько символов, я создаю несколько ChatViewModels. Я передаю персонажа в конструктор ChatViewModels и подписываюсь на событие чата. Я создаю ObservableColleciton, чтобы держать мои строки чата, когда это событие получено. Теперь я получаю проблему с потоками на моей модели viewModel при попытке добавить строку, которую я получаю из своего события чата, в мою наблюдаемую коллекцию.

Я обошел это, сделав так, чтобы мой обработчик событий viewmodels выглядел так:

public ObservableCollection<Game.ChatLine) Chat {get; private set;}

void Chat_Incoming(Game.ChatLine line)
{
  App.Current.Dispatcher.Invoke(new Action(delegate
  {
    Chat.Add(line)
  }), null);
}

Это мне не нравится. Хотя это работает, использование Диспетчера в моей модели просмотра, как это кажется мне неуместным.

4b9b3361

Ответ 1

Хотя это работает, использование Диспетчера в моей модели просмотра, как это кажется мне неуместным.

Это не совсем необоснованный подход, и это подход, который принимают многие люди. Лично, если вы используете WPF (или Silverlight 5) и имеете доступ к TPL, я предпочитаю использовать TPL для этого.

Предполагая, что ваша ViewModel построена на потоке пользовательского интерфейса (то есть: по представлению или в ответ на связанное с View событие), что почти всегда относится к IMO, вы можете добавить это в свой конструктор:

// Add to class:
TaskFactory uiFactory;

public MyViewModel()
{
    // Construct a TaskFactory that uses the UI thread context
    uiFactory = new TaskFactory(TaskScheduler.FromCurrentSynchronizationContext());
}

Затем, когда вы получите свое событие, вы можете использовать его для его маршала:

void Chat_Incoming(Game.ChatLine line)
{
    uiFactory.StartNew( () => Chat.Add(line) );
}

Обратите внимание, что это немного отличается от вашего оригинала, поскольку он больше не блокирует (это больше похоже на использование BeginInvoke вместо Invoke). Если вам нужно это заблокировать, пока пользовательский интерфейс не завершит обработку сообщения, вы можете использовать:

void Chat_Incoming(Game.ChatLine line)
{
    uiFactory.StartNew( () => Chat.Add(line) ).Wait();
}

Ответ 2

Модель просмотра - хорошее место для синхронизации потоков. Удалите DispatcherTimer из вашей модели и дайте VM обработать его.

Ответ 3

Я люблю ответ Рида и соглашаюсь с вашими опасениями, что что-то не так с вашим использованием Dispatcher. Ваши VM-ссылки App, что, на мой взгляд, является ссылкой на артефакт пользовательского интерфейса (или элемент управления). Вместо этого используйте Application или, что еще лучше, вставьте правильный экземпляр Dispatcher в вашу виртуальную машину, что избавит вас от необходимости создавать экземпляр вашей виртуальной машины в потоке пользовательского интерфейса.