Accesso sicuro al thread dell'interfaccia utente (principale) in WPF


95

Ho un'applicazione che aggiorna il mio datagrid ogni volta che un file di registro che sto guardando viene aggiornato (aggiunto con nuovo testo) nel modo seguente:

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);
    }

Quando l'evento viene generato per FileWatcher, perché crea un thread separato, quando provo a eseguire dataGridRows.Add (ds); per aggiungere la nuova riga, il programma va in crash senza alcun avviso dato durante la modalità di debug.

In Winforms, questo è stato facilmente risolto utilizzando la funzione Invoke, ma non sono sicuro di come procedere in WPF.

Risposte:


199

Puoi usare

Dispatcher.Invoke(Delegate, object[])

sul dispatcher di Application(o di qualsiasi altro UIElement).

Puoi usarlo ad esempio in questo modo:

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

o

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

L'approccio precedente ha fornito un errore perché Application.Current è null al momento dell'esecuzione della riga. Perché dovrebbe essere così?
l46kok

Puoi usare qualsiasi UIElement per questo, poiché ogni UIElement ha la proprietà "Dispatcher".
Wolfgang Ziegler

1
@ l46kok Ciò può avere diversi motivi (app console, hosting da winforms ecc.). Come ha detto @WolfgangZiegler, puoi usare qualsiasi UIElement per questo. Di solito lo uso Application.Currentsolo perché mi sembra più pulito.
Botz3000

@ Botz3000 Penso di avere anche qualche problema di condizione di gara che si verifica qui. Dopo aver aggiunto il codice sopra, il codice funziona perfettamente quando vado in modalità di debug ed eseguo manualmente i passaggi, ma il codice si arresta in modo anomalo quando eseguo l'applicazione senza debug. Non sono sicuro di cosa bloccare qui che sta causando un problema.
l46kok

1
@ l46kok Se pensi che sia una situazione di stallo, puoi anche chiamare Dispatcher.BeginInvoke. Questo metodo accoda solo il delegato per l'esecuzione.
Botz3000

50

Il modo migliore per farlo sarebbe ottenere un SynchronizationContextdal thread dell'interfaccia utente e usarlo. Questa classe astrae le chiamate di marshalling ad altri thread e semplifica i test (a differenza dell'uso Dispatcherdiretto di WPF ). Per esempio:

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);
    }
}

Molte grazie! La soluzione accettata si è bloccata ogni volta che è stata chiamata, ma funziona.
Dov

Funziona anche quando viene chiamato da un assembly contenente il modello di visualizzazione ma non un WPF "reale", ovvero è una libreria di classi.
Onur

Questo è un suggerimento molto utile, specialmente quando hai un componente non wpf con un thread su cui vuoi eseguire il marshalling delle azioni. Ovviamente un altro modo per farlo sarebbe usare le continuazioni TPL
MAYaN

All'inizio non lo capivo, ma per me ha funzionato ... bello. (Dovrebbe sottolineare che DGAddRow è un metodo privato)
Tim Davis

5

Utilizzare [Dispatcher.Invoke (DispatcherPriority, Delegate)] per modificare l'interfaccia utente da un altro thread o dallo sfondo.

Passaggio 1 . Utilizza i seguenti spazi dei nomi

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

Passaggio 2 . Metti la seguente riga in cui devi aggiornare l'interfaccia utente

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

Sintassi

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

Parametri

priority

Genere: System.Windows.Threading.DispatcherPriority

La priorità, rispetto alle altre operazioni in sospeso nella coda degli eventi del Dispatcher, viene richiamata dal metodo specificato.

method

Genere: System.Delegate

Un delegato a un metodo che non accetta argomenti, che viene inserito nella coda degli eventi del Dispatcher.

Valore di ritorno

Genere: System.Object

Il valore restituito dal delegato richiamato o null se il delegato non ha valore restituito.

Informazioni sulla versione

Disponibile da .NET Framework 3.0

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.