Najszybszy(?) sposób na aktualizację UI z innego wątku

Opublikowane 27 sierpnia 08 11:53 | chaniewski 

... czyli Kochamy Delegaty, Wyrażenia Lambda i Extension Methods :)

Pisząc aplikacje Windows nie raz i nie dwa zdarza się, że musimy zaktualizować zawartość interfejsu użytkownika z poziomu innego wątku (który np. wykonuje jakieś obliczenia lub wykonuje długotrwałą operację). Niestety nie możemy tego zrobić bezpośrednio przez ustawienie odpowiednich właściwości w kodzie. Spróbujmy w oddzielnym wątku ustawić tytuł formularza:

   1:  private void button1_Click(object sender, EventArgs e)
   2:  {
   3:      var asyncCode = new Thread(() => Text = "Ala ma kota") { IsBackground = true };
   4:      asyncCode.Start();
   5:  }

W efekcie otrzymamy elegancki wyjątek:

Cross-thread operation not valid: Control 'Form1' accessed from a thread other than the thread it was created on.

(dodatkowo widać, jak wyrażenia lambda upraszczają nasz kod - w tym przypadku wykonanie operacji asynchronicznie)

Cóż, trzeba to jakoś naprawić. W tym celu zrobimy sobie Extension Method, którego będziemy mogli używać na wszystkich obiektach dziedziczących z klasy Form.

   1:  public delegate void AsyncMethodInvoker();
   2:   
   3:  public static class FormAsyncUpdateExtension
   4:  {
   5:      public static void AsyncUpdate(this Form form, AsyncMethodInvoker asyncDelegate)
   6:      {
   7:          if(form.InvokeRequired)
   8:          {
   9:              form.Invoke(asyncDelegate);
  10:          }
  11:          else
  12:          {
  13:              asyncDelegate();
  14:          }
  15:      }
  16:  }
W linii pierwszej definiujemy delegata, który nam się dalej przyda (wiem wiem, mamy we Frameworku gotowy identyczny delegat MethodInvoker, ale przykładów nigdy za wiele). A zaraz potem używamy go do zdefiniowania metody rozszerzającej klasę Form. Od tej pory będziemy mogli pisać kod typu: moj_formularz.AsyncUpdate(() => moj_formularz.Text = "Ala ma kota");. Sprawdźmy:
   1:  private void button1_Click(object sender, EventArgs e)
   2:  {
   3:      var asyncCode = 
   4:          new Thread(() => 
   5:              this.AsyncUpdate(() => Text = "Ala ma kota")) 
   6:              { IsBackground = true };
   7:      asyncCode.Start();
   8:  }

Działa! Czy ktoś zna metodę, która po napisaniu raz, jest potem jeszcze zgrabniejsza w użyciu?

Filed under:

Powiadamianie o komentarzach

Jeżeli chciałbyś otrzymywać email gdy ta wypowiedź zostanie zaktualizowana, to zarejestruj się tutaj

Subskrybuj komentarze za pomocą RSS

Komentarze:

# Procent said on sierpnia 28, 2008 08:34:

Dla "pełnej poprawności" powinno się sprawdzać jeden warunek:

if (form.InvokeRequired)

form.Invoke(asyncDelegate);

else

asyncDelegate();

W ten sposób unikamy narzutu związanego z Invoke() jesli metoda wywołana jest z wątku UI.

# chaniewski said on sierpnia 28, 2008 08:43:

święta racja, poprawię kod

# Wojciech Gebczyk said on sierpnia 28, 2008 11:30:

Ja znam - sporo metod z [System.Core, Version=3.5.0.0]System.Linq.Enumerable a zwlaszcza Last czy LastOrDefault ;-). A ostatnio ulubione to Repeat, Range i ToArray. Zwlaszcza ze ... wogole nich nie trzeba pisac :-)))

BTW: Zakladam ze new Thread jest dla przykladu, bo jesli to typowe wywolanie to moze w srodek updatetera nalezalo by wrzucuc threadpoola. Niekiedy moze byc szybciej i oszczedzniej.

# Bysza said on sierpnia 29, 2008 10:01:

Artykuł o PostSharp autorstwa Gaela, który zostanie opublikowany w Zine v3, spowoduje zaoranie powyższego problemu, dzięki użyciu aspektów... :)

# mgrzeg said on sierpnia 29, 2008 10:33:

Marek, prawde rzeczesz! :)

A kto bedzie chcial zobaczyc aspekty w akcji, niech wpadnie na Dzien Zina w przyszla sobote :)

Co o tym myślisz?

(wymagane) 
(opcjonalne)
(wymagane) 

  
Wprowadź kod: (wymagane)