Безпечний доступ до UI (Main) Thread у WPF


99

У мене є програма, яка оновлює мою сітку даних кожного разу, коли файл журналу, який я спостерігаю, оновлюється (додається з новим текстом) наступним чином:

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

Коли подія викликається для FileWatcher, оскільки вона створює окремий потік, коли я намагаюся запустити dataGridRows.Add (ds); щоб додати новий рядок, програма просто виходить з ладу без попередження під час режиму налагодження.

У Winforms це легко було вирішити за допомогою функції Invoke, але я не впевнений, як це робити у WPF.

Відповіді:


203

Можна використовувати

Dispatcher.Invoke(Delegate, object[])

на диспетчері Application(або будь-якому іншому UIElement).

Ви можете використовувати його, наприклад, так:

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

або

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

Вищезазначений підхід дав помилку, оскільки Application.Current є нульовим під час запуску рядка. Чому це так?
— l46kok

Для цього ви можете просто використовувати будь-який UIElement, оскільки кожен UIElement має властивість "Dispatcher".
— Wolfgang Ziegler

1
@ l46kok Це може мати різні причини (консольний додаток, хостинг від winform тощо). Як сказав @WolfgangZiegler, для цього ви можете використовувати будь-який UIElement. Я просто зазвичай використовую Application.Currentдля нього, оскільки він здається мені чистішим.
— Botz3000

@ Botz3000 Я думаю, у мене тут також виникають певні проблеми з перегонами. Після додавання коду, наведеного вище, код чудово працює, коли я переходжу в режим налагодження та вручну роблю степовери, але код виходить з ладу, коли я запускаю програму без налагодження. Я не впевнений, що тут заблокувати, що спричиняє проблему.
— l46kok

1
@ l46kok Якщо ви вважаєте, що це тупик, ви також можете зателефонувати Dispatcher.BeginInvoke. Цей метод просто додає делегата до черги для виконання.
— Botz3000

51

Найкращий спосіб це зробити - отримати a SynchronizationContextз потоку інтерфейсу користувача та використовувати його. Цей клас абстрагується від маршальних викликів до інших потоків та полегшує тестування (на відміну від використання Dispatcherбезпосередньо WPF ). Наприклад:

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

Дуже дякую! Прийняте рішення починало звисати кожного разу, коли його викликали, але це працює.
— Дов

Він також працює, коли викликається зі збірки, що містить модель подання, але не має "реального" WPF, тобто є бібліотекою класів.
— Онур,

Це дуже корисна порада, особливо коли у вас є компонент, що не є wpf, із потоком, до якого ви хочете здійснити маршальські дії. звичайно, іншим способом зробити це буде використання продовжень TPL
— MaYaN

Спочатку я цього не розумів, але це спрацювало для мене .. приємно. (Слід зазначити, що DGAddRow - це приватний метод)
— Тім Девіс,

5

Використовуйте [Dispatcher.Invoke (DispatcherPriority, делегат)], щоб змінити користувальницький інтерфейс з іншого потоку або з фону.

Крок 1 . Використовуйте такі простори імен

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

Крок 2 . Поставте наступний рядок там, де потрібно оновити інтерфейс

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

Синтаксис

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

Параметри

priority

Тип: System.Windows.Threading.DispatcherPriority

Пріоритет, відносно інших очікуваних операцій у черзі подій диспетчера, викликається вказаним методом.

method

Тип: System.Delegate

Делегат методу, який не приймає аргументів, який надсилається до черги подій Dispatcher.

Повернене значення

Тип: System.Object

Повернене значення від делегата, який викликається, або нульовий, якщо делегат не має поверненого значення.

Інформація про версію

Доступно з .NET Framework 3.0

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.