Zmień kontrolki WPF z wątku innego niż główny przy użyciu Dispatcher.Invoke


81

Niedawno zacząłem programować w WPF i napotkałem następujący problem. Nie rozumiem, jak używać tej Dispatcher.Invoke()metody. Mam doświadczenie w tworzeniu wątków i stworzyłem kilka prostych programów Windows Forms, w których właśnie użyłem

Control.CheckForIllegalCrossThreadCalls = false;

Tak, wiem, że to dość kiepskie, ale były to proste aplikacje monitorujące.

Faktem jest, że teraz tworzę aplikację WPF, która pobiera dane w tle, zaczynam nowy wątek, aby wykonać wywołanie w celu pobrania danych (z serwera internetowego), teraz chcę wyświetlić je w moim formularzu WPF. Rzecz w tym, że nie mogę ustawić żadnej kontroli z tego wątku. Ani etykiety, ani nic. Jak można to rozwiązać?

Komentarze do odpowiedzi:
@Jalfp:
Więc używam tej metody Dispatchera w „nowym bieżniku”, kiedy otrzymuję dane? A może powinienem zmusić pracownika działającego w tle do pobrania danych, umieszczenia ich w polu i rozpoczęcia nowego wątku, który czeka, aż to pole zostanie wypełnione, i zadzwonić do dyspozytora, aby pokazał pobrane dane w kontrolkach?


To CheckForIllegalCrossThreadCalls jest niesamowite. Chciałbym wiedzieć, że zanim na szybkie „kogo to obchodzi” aplikacji
— Gaspa79

Odpowiedzi:


177

Pierwszą rzeczą jest zrozumienie, że Dispatcher nie jest przeznaczony do wykonywania długich operacji blokowania (takich jak pobieranie danych z serwera internetowego ...). Możesz użyć Dispatchera, gdy chcesz uruchomić operację, która zostanie wykonana w wątku interfejsu użytkownika (na przykład zaktualizowanie wartości paska postępu).

Możesz pobrać dane w tle procesu roboczego i użyć metody ReportProgress do propagowania zmian w wątku interfejsu użytkownika.

Jeśli naprawdę potrzebujesz bezpośrednio skorzystać z Dispatchera, jest to całkiem proste:

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

22
Możesz pozbyć się części „new Action (”) i po prostu użyć wyrażenia lambda: DispatcherPriority.Background, () => this.progressBar.Value = 50
— jrista

Tak, nie wiem, dlaczego umieściłem tutaj akcję: p
— japf

1
@Carsten Ta odpowiedź dotyczy aplikacji WPF, które używają klasy System.Windows.Application.
— joshuapoehls

10
@jrista: Naprawdę możesz? Otrzymuję CS1660 , próbując bez new Action(...).
— LUB Mapper

6
@jrista: Ogólnie rzecz biorąc, prawda - chociaż ten artykuł wyjaśnia, dlaczego nie działa w przypadku metod bez parametrów, takich jak te przekazywane do, BeginInvokea zamiast tego pojawia się błąd kompilatora CS1660.
— LUB Mapper

31

japf odpowiedział poprawnie. Na wszelki wypadek, jeśli patrzysz na akcje wieloliniowe, możesz napisać jak poniżej.

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

Informacje dla innych użytkowników, którzy chcą wiedzieć o wydajności:

Jeśli kod MUSI zostać napisany w celu uzyskania wysokiej wydajności, możesz najpierw sprawdzić, czy wywołanie jest wymagane, używając flagi CheckAccess.

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

Zwróć uwagę, że metoda CheckAccess () jest ukryta w programie Visual Studio 2015, więc po prostu napisz ją, nie oczekując, że inteligencja ją pokaże. Zauważ, że CheckAccess ma narzut na wydajność (narzut w ciągu kilku nanosekund). Lepiej jest tylko wtedy, gdy chcesz zaoszczędzić tę mikrosekundę wymaganą do wykonania „wywołania” za wszelką cenę. Ponadto zawsze istnieje opcja utworzenia dwóch metod (włączonych z invoke i innych bez), gdy wywołanie metody jest pewne, czy jest w wątku interfejsu użytkownika, czy nie. To bardzo rzadki przypadek, kiedy powinieneś spojrzeć na ten aspekt dyspozytora.


4

Kiedy wykonywany jest wątek i chcesz wykonać główny wątek interfejsu użytkownika, który jest blokowany przez bieżący wątek, użyj poniższego:

aktualny wątek:

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

Główny wątek interfejsu użytkownika:

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

MethodName nie istnieje w obecnym kontekście
— AriesConnolly

0

Powyższa odpowiedź @japf działa dobrze, aw moim przypadku chciałem zmienić kursor myszy z obracającego się koła z powrotem na normalną strzałkę, gdy przeglądarka CEF zakończy ładowanie strony. W przypadku, gdy może to komuś pomóc, oto kod:

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));
   }
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.