c# - que - ¿Es incorrecto usar el Dispatcher dentro de mi ViewModel?
uwp observablecollection class (3)
Estoy convirtiendo un analizador de chat para un juego que escribí en c # winforms a wpf, principalmente para obtener un mejor control sobre MVVM y wpf. Aquí está un resumen de cómo tengo mi proyecto configurado
Vista : por ahora solo es un simple ListBox con ItemSource vinculado a mis modelos de chat observables de viewmodels
Modelo : tengo varios caracteres que pueden iniciar sesión a la vez y cada personaje tiene una clase de chat. La clase de chat inicia a un trabajador en segundo plano que capta la siguiente línea de chat del juego y dispara un evento llamado IncomingChat con esta línea.
public event Action<Game.ChatLine> IncomingChat;
Estoy usando un trabajador de fondo para activar un evento en el evento progresschaged de mis trabajadores de fondo porque cuando estaba usando un temporizador seguí teniendo un problema de subprocesos. Al principio corregí esto cambiando mi temporizador a un DispatchTimer, pero no me parecía correcto tener un DispatchTimer en mi modelo.
ViewModel : ya que tengo varios caracteres, estoy creando varios ChatViewModels. Paso un personaje al constructor ChatViewModels y me suscribo al evento Chat. Creo un ObservableColleciton para mantener mis líneas de chat cuando se recibe este evento. Ahora recibo un problema de subprocesos en mi viewModel cuando intento agregar la línea que recibo de mi evento de chat a mi colección de observación.
Resolví esto haciendo que mis controladores de eventos de chat entrantes de viewmodels se vieran así
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);
}
Aunque esto no me parece bien. Aunque funciona, usar Dispatcher en mi modelo de vista como este me parece fuera de lugar.
Aunque funciona, usar Dispatcher en mi modelo de vista como este me parece fuera de lugar.
Este no es un enfoque completamente irrazonable, y es el enfoque que muchas personas toman. Personalmente, si está usando WPF (o Silverlight 5), y tiene acceso a la TPL, prefiero usar la TPL para manejar esto.
Suponiendo que su ViewModel está construido en el subproceso de la interfaz de usuario (es decir, por la vista, o en respuesta a un evento relacionado con la vista), que es el caso casi siempre de IMO, puede agregar esto a su constructor:
// Add to class:
TaskFactory uiFactory;
public MyViewModel()
{
// Construct a TaskFactory that uses the UI thread''s context
uiFactory = new TaskFactory(TaskScheduler.FromCurrentSynchronizationContext());
}
Luego, cuando obtenga su evento, puede usar esto para organizarlo:
void Chat_Incoming(Game.ChatLine line)
{
uiFactory.StartNew( () => Chat.Add(line) );
}
Tenga en cuenta que esto es ligeramente diferente a su original, ya que ya no está bloqueando (es más como usar BeginInvoke
lugar de Invoke
). Si necesita bloquear esto hasta que la interfaz de usuario termine de procesar el mensaje, puede usar:
void Chat_Incoming(Game.ChatLine line)
{
uiFactory.StartNew( () => Chat.Add(line) ).Wait();
}
Me encanta la respuesta de Reed y estoy de acuerdo con sus preocupaciones de que algo no está bien con su uso del Dispatcher
. Su VM hace referencia a la App
, que es, en mi opinión, una referencia a un artefacto (o control) de UI. Use la Application
lugar o, mejor aún, inyecte la instancia correcta de Dispatcher
en su máquina virtual, lo que evita la necesidad de crear una instancia de su máquina virtual en el hilo de la interfaz de usuario.
Un modelo de vista es un buen lugar para realizar una sincronización de subprocesos. Elimine DispatcherTimer de su modelo y deje que la VM lo maneje.