Mengakses UI (Utama) Thread dengan aman di WPF

99

Saya memiliki aplikasi yang memperbarui datagrid saya setiap kali file log yang saya tonton diperbarui (Ditambahkan dengan teks baru) dengan cara berikut:

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

Ketika acara dimunculkan untuk FileWatcher, karena itu membuat utas terpisah, ketika saya mencoba menjalankan dataGridRows.Add (ds); untuk menambahkan baris baru, program hanya macet tanpa peringatan apa pun yang diberikan selama mode debug.

Di Winforms, ini mudah diselesaikan dengan memanfaatkan fungsi Invoke tetapi saya tidak yakin bagaimana cara melakukannya di WPF.

l46kok
sumber

Jawaban:

203

Kamu bisa memakai

Dispatcher.Invoke(Delegate, object[])

di operator Application(atau yang lainnya UIElement).

Anda bisa menggunakannya misalnya seperti ini:

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

atau

someControl.Dispatcher.Invoke(new Action(() => { /* Your code here */ }));
Botz3000
sumber
Pendekatan di atas memberikan kesalahan karena Application.Current adalah null pada saat menjalankan saluran. Mengapa demikian?
l46kok
Anda bisa menggunakan UIElement apa saja untuk itu, karena setiap UIElement memiliki properti "Dispatcher".
Wolfgang Ziegler
1
@ l46kok Ini dapat memiliki alasan yang berbeda (aplikasi konsol, hosting dari winforms, dll.). Seperti yang dikatakan @WolfgangZiegler, Anda dapat menggunakan UIElement apa pun untuk itu. Saya biasanya menggunakannya Application.Currentkarena terlihat lebih bersih bagi saya.
Botz3000
@ Botz3000 Saya rasa saya juga memiliki beberapa masalah kondisi balapan yang terjadi di sini. Setelah menambahkan kode yang diberikan di atas, kode bekerja dengan sempurna ketika saya masuk ke mode debug dan secara manual melakukan stepovers, tetapi kode macet ketika saya menjalankan aplikasi tanpa debug. Saya tidak yakin apa yang harus dikunci di sini yang menyebabkan masalah.
l46kok
1
@ l46kok Jika menurut Anda ini jalan buntu, Anda juga dapat menelepon Dispatcher.BeginInvoke. Metode itu hanya mengantrekan delegasi untuk dieksekusi.
Botz3000
52

Cara terbaik untuk melakukannya adalah dengan mendapatkan SynchronizationContextdari UI thread dan menggunakannya. Kelas ini memisahkan panggilan marshalling ke thread lain, dan membuat pengujian lebih mudah (berbeda dengan menggunakan WPF Dispatchersecara langsung). Sebagai contoh:

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
sumber
Terima kasih banyak! Solusi yang diterima mulai menggantung setiap kali dipanggil, tetapi ini berhasil.
Dov
Ia juga bekerja ketika dipanggil dari rakitan yang berisi model tampilan tetapi tidak ada WPF "nyata", yaitu perpustakaan kelas.
Onur
Ini adalah tip yang sangat berguna, khususnya ketika Anda memiliki komponen non-wpf dengan utas yang ingin Anda lakukan tindakan. tentu saja cara lain untuk melakukannya adalah dengan menggunakan TPL lanjutan
MaYaN
Saya tidak memahaminya pada awalnya, tapi itu berhasil untuk saya .. bagus. (Harus menunjukkan DGAddRow adalah metode pribadi)
Tim Davis
5

Gunakan [Dispatcher.Invoke (DispatcherPriority, Delegate)] untuk mengubah UI dari thread lain atau dari latar belakang.

LANGKAH 1 . Gunakan ruang nama berikut

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

LANGKAH 2 . Letakkan baris berikut di mana Anda perlu memperbarui UI

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

Sintaksis

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

Parameter

priority

Tipe: System.Windows.Threading.DispatcherPriority

Prioritasnya, relatif terhadap operasi lain yang tertunda dalam antrian acara Dispatcher, metode yang ditentukan akan dipanggil.

method

Tipe: System.Delegate

Delegasi ke metode yang tidak membutuhkan argumen, yang didorong ke antrean peristiwa Dispatcher.

Nilai Kembali

Tipe: System.Object

Nilai kembalian dari delegasi yang dipanggil atau null jika delegasi tidak memiliki nilai kembalian.

Informasi Versi

Tersedia sejak .NET Framework 3.0

Vineet Choudhary
sumber