Змініть елементи керування WPF з не основного потоку за допомогою Dispatcher.Invoke


81

Нещодавно я почав програмувати у WPF і наткнувся на наступну проблему. Я не розумію, як використовувати Dispatcher.Invoke()метод. Я маю досвід роботи з потоками і створив кілька простих програм Windows Forms, де я щойно використовував

Control.CheckForIllegalCrossThreadCalls = false;

Так, я знаю, що це досить кульгаво, але це були прості програми моніторингу.

Справа в тому, що зараз я роблю програму WPF, яка отримує дані у фоновому режимі, я запускаю новий потік, щоб здійснити виклик для отримання даних (з веб-сервера), тепер я хочу відобразити це у моїй формі WPF. Справа в тому, що я не можу встановити будь-який контроль із цієї теми. Навіть етикетки чи чогось іншого. Як це можна вирішити?

Відповісти на коментарі:
@Jalfp:
Отже, я використовую цей метод Dispatcher у "новому протекторі", коли отримую дані? Або я повинен змусити фонового працівника отримувати дані, поміщати їх у поле і запускати новий потік, який чекає, поки це поле заповниться, і викликати диспетчера, щоб показані отримані дані були в елементах керування?


Цей CheckForIllegalCrossThreadCalls є приголомшливим. Хотілося б, щоб я знав це раніше для швидких додатків "хто дбає"
Gaspa79

Відповіді:


177

Перше, що потрібно зрозуміти, Диспетчер не призначений для тривалої операції блокування (наприклад, отримання даних з WebServer ...). Ви можете використовувати диспетчер, коли хочете запустити операцію, яка буде виконана в потоці інтерфейсу користувача (наприклад, оновлення значення індикатора прогресу).

Що ви можете зробити, це отримати свої дані у фоновому режимі та використовувати метод ReportProgress для поширення змін у потоці інтерфейсу користувача.

Якщо вам дійсно потрібно використовувати Dispatcher безпосередньо, це досить просто:

Application.Current.Dispatcher.BeginInvoke(
  DispatcherPriority.Background,
  new Action(() => this.progressBar.Value = 50));

22
Ви можете позбутися частини "new Action (") і просто використати лямбда-вираз: DispatcherPriority.Background, () => this.progressBar.Value = 50
jrista

Так, не знаю, чому я ставлю тут Action: p
japf

1
@Carsten Ця відповідь стосується програм WPF, які використовують клас System.Windows.Application.
joshuapoehls

10
@jrista: Ви можете справді? Я отримую CS1660 при спробі без new Action(...).
АБО Mapper

6
@jrista: Загалом, правда - хоча ця стаття пояснює, чому це не працює у випадку BeginInvokeбезпараметричних методів, таких як ті, що передаються, і замість цього видається помилка компілятора CS1660.
АБО Mapper

31

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

Application.Current.Dispatcher.BeginInvoke(
  DispatcherPriority.Background,
  new Action(() => { 
    this.progressBar.Value = 50;
  }));

Інформація для інших користувачів, які хочуть знати про ефективність:

Якщо ваш код ПОТРІБНО писати для високої продуктивності, ви можете спочатку перевірити, чи потрібен виклик, використовуючи прапор CheckAccess.

if(Application.Current.Dispatcher.CheckAccess())
{
    this.progressBar.Value = 50;
}
else
{
    Application.Current.Dispatcher.BeginInvoke(
      DispatcherPriority.Background,
      new Action(() => { 
        this.progressBar.Value = 50;
      }));
}

Зверніть увагу, що метод CheckAccess () прихований від Visual Studio 2015, тому просто напишіть його, не очікуючи, що це відобразить intellisense. Зверніть увагу, що CheckAccess має накладні витрати на продуктивність (накладні витрати за кілька наносекунд). Це краще лише тоді, коли ви хочете за будь-яку ціну заощадити цю мікросекунду, необхідну для виконання "виклику". Крім того, завжди є можливість створити два методи (on with invoke та інші без), коли метод виклику впевнений, знаходиться він у UI Thread чи ні. Це найрідкісніший рідкісний випадок, коли вам слід дивитись на цей аспект диспетчера.


4

Коли потік виконується, і ви хочете виконати основний потік інтерфейсу, який заблокований поточним потоком, використовуйте наступне:

поточний потік:

Dispatcher.CurrentDispatcher.Invoke(MethodName,
    new object[] { parameter1, parameter2 }); // if passing 2 parameters to method.

Основний потік інтерфейсу користувача:

Application.Current.Dispatcher.BeginInvoke(
    DispatcherPriority.Background, new Action(() => MethodName(parameter)));

MethodName не існує у поточному контексті
AriesConnolly

0

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

private void Browser_LoadingStateChanged(object sender, CefSharp.LoadingStateChangedEventArgs e) {
   if (!e.IsLoading) {
      // set the cursor back to arrow
      Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background,
         new Action(() => Mouse.OverrideCursor = Cursors.Arrow));
   }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.