czwartek, 9 kwietnia 2020

C# - Użycie kontrolek z innego wątku na przykładzie WPF

W tym poście chciałbym krótko wyjaśnić w jaki sposób obsługiwać kontrolki z innego wątku.

[Źródło: https://docs.microsoft.com/en-us/dotnet/]

Często zdarza się, że np. zachodzi potrzeba wykorzystania klasy BackgroundWorker, czy stworzenie nowego wątku Thread z których trzeba zaktualizować dane udostępnione w kontrolkach na ekranie.

Podczas tworzenia kontrolek w przygotowywanej aplikacji są one dodawane do wątku głównego. Gdy zostaje stworzony osobny wątek to nie można bezpośrednio odnieść się do kontrolki stworzonej w innym wątku. Spowoduje to wygenerowanie następującego błędu:


W przykładzie wykorzystuje klasę BackgroundWorker do pobierania ustawień przesłanych przez TCP Klient do TCP Serwer. TCP Klient jest to przygotowywana aplikacja w C#. TCP Serwer jest układ Arduino Nano wraz z modułem ENC28J60. Program przesyła i ustawia parametry sieciowe oraz kilka dodatkowych ustawień. 

Jeśli chcemy wykorzystywać jedną funkcję aktualizującą dane zarówno wątku głównym jak i pobocznym to można wykonać sprawdzanie który wątek wywołał daną funkcję. 

Dostęp do kontrolek z poziomu funkcji wykonywanej przez klasę BackgroundWorker wygląda następująco:

Poniżej wykonałem wpisanie wartości za pomocą kilki metod:

  1. newIpTxtBox.Dispatcher.Invoke(() => { newIpTxtBox.Text = ipValue; });
  2. newMaskTxtBox.Dispatcher.BeginInvoke(new Action(() => { newMaskTxtBox.Text = maskValue; }));
  3. newGateTxtBox.Dispatcher.Invoke(delegate { newGateTxtBox.Text = gateValue; });
  4. newPortTxtBox.Dispatcher.Invoke(() => { newPortTxtBox.Text = portValue; });
  5. DHCPEnableCheckBox.Dispatcher.Invoke(() => { DHCPEnableCheckBox.IsChecked = Convert.ToBoolean(dhcpValue); });
  6. alarmLengthTxtBox.Dispatcher.Invoke(() => { alarmLengthTxtBox.Text = alarmTimeValue; });
  7. askTimeTxtBox.Dispatcher.Invoke(() => { askTimeTxtBox.Text = askingTimeValue; });
  8. textBox1.Dispatcher.Invoke(new Action(delegate () { textBox1.AppendText("---" + "\n"); }));

Jak można zauważyć powyżej wykorzystywana jest klasa Dispatcher. Przekazuje ona wykonanie zadania do wątku w którym dana kontrolka została stworzona.
Można tu wyróżnić dwie funkcje Invoke oraz BeginInvoke. Pierwsza z nich pozwala na synchroniczne wykonanie zadania. Druga natomiast pozwala na wykonanie danego delegata asynchronicznie.

W przypadku korzystania z WindowsForm można wykorzystać np. taką instrukcję:

  1. textBox1.Invoke(new Action(delegate () { textBox1.AppendText("---" + "\n"); }));

Żródła:

[1] https://docs.microsoft.com/pl-pl/dotnet/api/system.windows.threading.dispatcher?view=netframework-4.8