Cambie los controles de WPF desde un subproceso no principal mediante Dispatcher.Invoke

81

Recientemente comencé a programar en WPF y me encontré con el siguiente problema. No entiendo cómo usar el Dispatcher.Invoke()método. Tengo experiencia en subprocesos y he creado algunos programas sencillos de Windows Forms en los que acabo de usar el

Control.CheckForIllegalCrossThreadCalls = false;

Sí, sé que es bastante tonto, pero estas eran aplicaciones de monitoreo simples.

El hecho es que ahora estoy creando una aplicación WPF que recupera datos en segundo plano, comienzo un nuevo hilo para hacer la llamada para recuperar los datos (desde un servidor web), ahora quiero mostrarlo en mi formulario WPF. La cuestión es que no puedo establecer ningún control desde este hilo. Ni siquiera una etiqueta ni nada. ¿Como puede ésto ser resuelto?

Comentarios de respuesta:
@Jalfp:
¿Entonces uso este método Dispatcher en la 'nueva banda de rodadura' cuando obtengo los datos? ¿O debería hacer que un trabajador en segundo plano recupere los datos, los ponga en un campo e inicie un nuevo hilo que espera hasta que este campo se complete y llame al despachador para mostrar los datos recuperados en los controles?

D. Veloper
fuente
Ese CheckForIllegalCrossThreadCalls es increíble. El deseo I sabía que antes para una rápida "a quién le importa" aplicaciones
Gaspa79

Respuestas:

177

Lo primero es entender que el Dispatcher no está diseñado para ejecutar operaciones de bloqueo largas (como recuperar datos de un servidor web ...). Puede usar el Dispatcher cuando desee ejecutar una operación que se ejecutará en el subproceso de la interfaz de usuario (como actualizar el valor de una barra de progreso).

Lo que puede hacer es recuperar sus datos en un trabajador en segundo plano y usar el método ReportProgress para propagar cambios en el subproceso de la interfaz de usuario.

Si realmente necesita usar Dispatcher directamente, es bastante simple:

Application.Current.Dispatcher.BeginInvoke(
  DispatcherPriority.Background,
  new Action(() => this.progressBar.Value = 50));
japf
fuente
22
Puede deshacerse de la parte 'new Action (' y simplemente usar una expresión lambda: DispatcherPriority.Background, () => this.progressBar.Value = 50
jrista
Sí, no sé por qué puse una acción aquí: p
japf
1
@Carsten Esta respuesta es para aplicaciones WPF que usan la clase System.Windows.Application.
Joshuapoehls
10
@jrista: ¿De verdad? Me estoy poniendo CS1660 al intentar sin new Action(...).
OR Mapper
6
@jrista: En general, es cierto, aunque este artículo explica por qué no funciona en el caso de métodos sin parámetros como los que se pasan BeginInvokey, en su lugar, se produce el error del compilador CS1660.
OR Mapper
31

japf ha respondido correctamente. Por si acaso, si está buscando acciones de varias líneas, puede escribir lo siguiente.

Application.Current.Dispatcher.BeginInvoke(
  DispatcherPriority.Background,
  new Action(() => { 
    this.progressBar.Value = 50;
  }));

Información para otros usuarios que quieran conocer el rendimiento:

Si su código NECESITA ser escrito para un alto rendimiento, primero puede verificar si la invocación es requerida usando el indicador CheckAccess.

if(Application.Current.Dispatcher.CheckAccess())
{
    this.progressBar.Value = 50;
}
else
{
    Application.Current.Dispatcher.BeginInvoke(
      DispatcherPriority.Background,
      new Action(() => { 
        this.progressBar.Value = 50;
      }));
}

Tenga en cuenta que el método CheckAccess () está oculto en Visual Studio 2015, así que escríbalo sin esperar que intellisense lo muestre. Tenga en cuenta que CheckAccess tiene una sobrecarga de rendimiento (sobrecarga en unos pocos nanosegundos). Solo es mejor cuando desea ahorrar ese microsegundo requerido para realizar la 'invocación' a cualquier costo. Además, siempre hay una opción para crear dos métodos (activado con invocación y otro sin) cuando el método de llamada está seguro de si está en UI Thread o no. Es solo un caso muy raro en el que debería estar mirando este aspecto del despachador.

Yogee
fuente
4

Cuando se está ejecutando un hilo y desea ejecutar el hilo principal de la IU que está bloqueado por el hilo actual, utilice lo siguiente:

hilo actual:

Dispatcher.CurrentDispatcher.Invoke(MethodName,
    new object[] { parameter1, parameter2 }); // if passing 2 parameters to method.

Hilo principal de la interfaz de usuario:

Application.Current.Dispatcher.BeginInvoke(
    DispatcherPriority.Background, new Action(() => MethodName(parameter)));
Gopi Rao
fuente
MethodName no existe en el contexto actual
AriesConnolly
0

La respuesta de @japf anterior funciona bien y, en mi caso, quería cambiar el cursor del mouse de una Rueda giratoria a la Flecha normal una vez que el Navegador CEF terminó de cargar la página. En caso de que pueda ayudar a alguien, aquí está el código:

private void Browser_LoadingStateChanged(object sender, CefSharp.LoadingStateChangedEventArgs e) {
   if (!e.IsLoading) {
      // set the cursor back to arrow
      Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background,
         new Action(() => Mouse.OverrideCursor = Cursors.Arrow));
   }
}
nrod
fuente