Безпечний доступ до 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.