Acceder al subproceso de la interfaz de usuario (principal) de forma segura en WPF

99

Tengo una aplicación que actualiza mi cuadrícula de datos cada vez que se actualiza un archivo de registro que estoy viendo (se agrega con texto nuevo) de la siguiente manera:

private void DGAddRow(string name, FunctionType ft)
    {
                ASCIIEncoding ascii = new ASCIIEncoding();

    CommDGDataSource ds = new CommDGDataSource();

    int position = 0;
    string[] data_split = ft.Data.Split(' ');
    foreach (AttributeType at in ft.Types)
    {
        if (at.IsAddress)
        {

            ds.Source = HexString2Ascii(data_split[position]);
            ds.Destination = HexString2Ascii(data_split[position+1]);
            break;
        }
        else
        {
            position += at.Size;
        }
    }
    ds.Protocol = name;
    ds.Number = rowCount;
    ds.Data = ft.Data;
    ds.Time = ft.Time;

    dataGridRows.Add(ds); 

    rowCount++;
    }
    ...
    private void FileSystemWatcher()
    {
        FileSystemWatcher watcher = new FileSystemWatcher(Environment.CurrentDirectory);
        watcher.Filter = syslogPath;
        watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite
            | NotifyFilters.FileName | NotifyFilters.DirectoryName;
        watcher.Changed += new FileSystemEventHandler(watcher_Changed);
        watcher.EnableRaisingEvents = true;
    }

    private void watcher_Changed(object sender, FileSystemEventArgs e)
    {
        if (File.Exists(syslogPath))
        {
            string line = GetLine(syslogPath,currentLine);
            foreach (CommRuleParser crp in crpList)
            {
                FunctionType ft = new FunctionType();
                if (crp.ParseLine(line, out ft))
                {
                    DGAddRow(crp.Protocol, ft);
                }
            }
            currentLine++;
        }
        else
            MessageBox.Show(UIConstant.COMM_SYSLOG_NON_EXIST_WARNING);
    }

Cuando se genera el evento para el FileWatcher, porque crea un hilo separado, cuando intento ejecutar dataGridRows.Add (ds); para agregar la nueva fila, el programa simplemente se bloquea sin ninguna advertencia durante el modo de depuración.

En Winforms, esto se resolvió fácilmente utilizando la función Invoke, pero no estoy seguro de cómo hacerlo en WPF.

l46kok
fuente

Respuestas:

204

Puedes usar

Dispatcher.Invoke(Delegate, object[])

en el despachador de Application's (o de cualquiera UIElement).

Puedes usarlo, por ejemplo, así:

Application.Current.Dispatcher.Invoke(new Action(() => { /* Your code here */ }));

o

someControl.Dispatcher.Invoke(new Action(() => { /* Your code here */ }));
Botz3000
fuente
El enfoque anterior estaba dando un error porque Application.Current es nulo en el momento de ejecutar la línea. Por qué sería este el caso?
l46kok
Puede usar cualquier UIElement para eso, ya que cada UIElement tiene la propiedad "Dispatcher".
Wolfgang Ziegler
1
@ l46kok Esto puede tener diferentes motivos (aplicación de consola, hosting de winforms, etc.). Como dijo @WolfgangZiegler, puede usar cualquier UIElement para ello. Normalmente lo uso Application.Currentporque me parece más limpio.
Botz3000
@ Botz3000 Creo que también tengo algunos problemas de condición de carrera aquí. Después de agregar el código proporcionado anteriormente, el código funciona perfectamente cuando entro en el modo de depuración y realizo pasos manualmente, pero el código falla cuando ejecuto la aplicación sin depurar. No estoy seguro de qué bloquear aquí que está causando un problema.
l46kok
1
@ l46kok Si cree que hay un punto muerto, también puede llamar Dispatcher.BeginInvoke. Ese método simplemente pone en cola al delegado para su ejecución.
Botz3000
52

La mejor manera de hacerlo sería obtener una SynchronizationContext del hilo de la interfaz de usuario y usarlo. Esta clase abstrae las llamadas de clasificación a otros subprocesos y facilita las pruebas (en contraste con el uso Dispatcherdirecto de WPF ). Por ejemplo:

class MyViewModel
{
    private readonly SynchronizationContext _syncContext;

    public MyViewModel()
    {
        // we assume this ctor is called from the UI thread!
        _syncContext = SynchronizationContext.Current;
    }

    // ...

    private void watcher_Changed(object sender, FileSystemEventArgs e)
    {
         _syncContext.Post(o => DGAddRow(crp.Protocol, ft), null);
    }
}
Eli Arbel
fuente
¡Muchas gracias! La solución aceptada comenzó a colgarse cada vez que se llamaba, pero esto funciona.
Dov
También funciona cuando se llama desde un ensamblado que contiene el modelo de vista pero no WPF "real", es decir, es una biblioteca de clases.
Onur
Este es un consejo muy útil, especialmente cuando tiene un componente que no es wpf con un hilo al que desea ordenar acciones. por supuesto, otra manera de hacerlo sería utilizar continuaciones TPL
Maya
No lo entendí al principio, pero funcionó para mí ... bueno. (Debería señalar que DGAddRow es un método privado)
Tim Davis
5

Utilice [Dispatcher.Invoke (DispatcherPriority, Delegate)] para cambiar la interfaz de usuario desde otro hilo o desde el fondo.

Paso 1 . Utilice los siguientes espacios de nombres

using System.Windows;
using System.Threading;
using System.Windows.Threading;

Paso 2 . Coloque la siguiente línea donde necesita actualizar la interfaz de usuario

Application.Current.Dispatcher.Invoke(DispatcherPriority.Background, new ThreadStart(delegate
{
    //Update UI here
}));

Sintaxis

[BrowsableAttribute(false)]
public object Invoke(
  DispatcherPriority priority,
  Delegate method
)

Parámetros

priority

Tipo: System.Windows.Threading.DispatcherPriority

Se invoca la prioridad, en relación con las otras operaciones pendientes en la cola de eventos de Dispatcher, el método especificado.

method

Tipo: System.Delegate

Un delegado de un método que no acepta argumentos, que se inserta en la cola de eventos de Dispatcher.

Valor devuelto

Tipo: System.Object

El valor de retorno del delegado que se invoca o es nulo si el delegado no tiene valor de retorno.

Información de versión

Disponible desde .NET Framework 3.0

Vineet Choudhary
fuente