Sicherer Zugriff auf UI (Main) Thread in WPF

94

Ich habe eine Anwendung, die mein Datagrid jedes Mal aktualisiert, wenn eine von mir beobachtete Protokolldatei auf folgende Weise aktualisiert (mit neuem Text angehängt) wird:

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

Wenn das Ereignis für den FileWatcher ausgelöst wird, weil es einen separaten Thread erstellt, wenn ich versuche, dataGridRows.Add (ds) auszuführen; Um die neue Zeile hinzuzufügen, stürzt das Programm nur ab, ohne dass im Debug-Modus eine Warnung ausgegeben wird.

In Winforms wurde dies leicht mithilfe der Invoke-Funktion gelöst, aber ich bin mir nicht sicher, wie ich dies in WPF tun soll.

l46kok
quelle

Antworten:

197

Sie können verwenden

Dispatcher.Invoke(Delegate, object[])

auf dem Application's (oder einemUIElement Dispatcher ).

Sie können es zum Beispiel so verwenden:

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

oder

someControl.Dispatcher.Invoke(new Action(() => { /* Your code here */ }));
Botz3000
quelle
Der obige Ansatz gab einen Fehler aus, da Application.Current zum Zeitpunkt des Ausführens der Zeile null ist. Warum sollte das so sein?
l46kok
Sie können dafür einfach ein beliebiges UIElement verwenden, da jedes UIElement die Eigenschaft "Dispatcher" hat.
Wolfgang Ziegler
1
@ l46kok Dies kann verschiedene Gründe haben (Konsolen-App, Hosting von Winforms usw.). Wie @WolfgangZiegler sagte, können Sie jedes UIElement dafür verwenden. Ich benutze Application.Currentes normalerweise , da es für mich sauberer aussieht.
Botz3000
@ Botz3000 Ich glaube, ich habe hier auch ein Problem mit den Rennbedingungen. Nach dem Anhängen des oben angegebenen Codes funktioniert der Code einwandfrei, wenn ich in den Debug-Modus gehe und manuell Übergänge mache. Der Code stürzt jedoch ab, wenn ich die Anwendung ohne Debug ausführe. Ich bin nicht sicher, was ich hier sperren soll, was ein Problem verursacht.
l46kok
1
@ l46kok Wenn Sie denken, dass es ein Deadlock ist, können Sie auch anrufen Dispatcher.BeginInvoke. Diese Methode stellt den Delegaten nur zur Ausführung in die Warteschlange.
Botz3000
49

Der beste Weg, dies zu tun, wäre, einen SynchronizationContextaus dem UI-Thread zu holen und ihn zu verwenden. Diese Klasse abstrahiert Marshalling-Aufrufe an andere Threads und erleichtert das Testen (im Gegensatz zur Dispatcherdirekten Verwendung von WPFs ). Beispielsweise:

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
quelle
Vielen Dank! Die akzeptierte Lösung begann jedes Mal zu hängen, wenn sie aufgerufen wurde, aber das funktioniert.
Dov
Es funktioniert auch, wenn es von einer Assembly aufgerufen wird, die das Ansichtsmodell enthält, aber keine "echte" WPF, dh eine Klassenbibliothek.
Onur
Dies ist ein sehr nützlicher Tipp, insbesondere wenn Sie eine Nicht-Wpf-Komponente mit einem Thread haben, für den Sie Aktionen zusammenstellen möchten. Natürlich wäre eine andere Möglichkeit, TPL-Fortsetzungen zu verwenden
MaYaN
Ich habe es zuerst nicht verstanden, aber es hat bei mir funktioniert ... schön. (Sollte darauf hinweisen, dass DGAddRow eine private Methode ist)
Tim Davis
5

Verwenden Sie [Dispatcher.Invoke (DispatcherPriority, Delegate)] , um die Benutzeroberfläche von einem anderen Thread oder vom Hintergrund aus zu ändern.

Schritt 1 . Verwenden Sie die folgenden Namespaces

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

Schritt 2 . Fügen Sie die folgende Zeile ein, in der Sie die Benutzeroberfläche aktualisieren müssen

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

Syntax

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

Parameter

priority

Art: System.Windows.Threading.DispatcherPriority

Die Priorität in Bezug auf die anderen ausstehenden Vorgänge in der Dispatcher-Ereigniswarteschlange, die angegebene Methode, wird aufgerufen.

method

Art: System.Delegate

Ein Delegat an eine Methode, die keine Argumente akzeptiert und in die Dispatcher-Ereigniswarteschlange verschoben wird.

Rückgabewert

Art: System.Object

Der Rückgabewert des aufgerufenen Delegaten oder null, wenn der Delegat keinen Rückgabewert hat.

Versionsinformation

Verfügbar seit .NET Framework 3.0

Vineet Choudhary
quelle