Неправильно ли использовать Dispatcher в моей ViewModel? - PullRequest
18 голосов
/ 19 сентября 2011

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

View : На данный момент это всего лишь простой ListBox с ItemSource, привязанным к моей коллекции чатов viewmodels

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

public event Action<Game.ChatLine> IncomingChat;

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

ViewModel : Так как у меня есть несколько персонажей, я создаю несколько ChatViewModels. Я передаю символ в конструктор ChatViewModels и подписываюсь на событие Chat. Я создаю 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);
}

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

Ответы [ 3 ]

38 голосов
/ 19 сентября 2011

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

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

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

// Add to class:
TaskFactory uiFactory;

public MyViewModel()
{
    // Construct a TaskFactory that uses the UI thread's 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();
}
0 голосов
/ 19 сентября 2011

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

0 голосов
/ 19 сентября 2011

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

...