Przejdź do treści

Programowanie wielowątkowe

Uruchomienie kodu w osobnym wątku

Serwer przeznacza dla każdego klienta jeden wątek swojego działania. Oznacza to, że jeśli klient wywoła jakąś akcję i skomunikuje się z serwerem, to klient czeka aż serwer zakończy wszelkie działania związane z tą akcją, po czym odbiera od serwera zestaw komunikatów informujący o potrzebnych zmianach po stronie klienta (np. zmiana wyglądu formy, pokazanie nowej formy, itp). Jeśli dana akcja trwa po stronie serwera dosyć długo, to aplikacja klienta jest zamrożona przez cały ten czas. Jeśli kod C# akcji po stronie serwera zawiera na początku komendy zmieniające wygląd interfejsu po stronie klienta, to komendy te zostaną przez klienta odebrane dopiero wtedy, gdy serwer zakończy całą akcję. Nie da się więc w prosty sposób poinformować klienta, że trwa dłuższe przetwarzanie danych.

Aby rozwiązać ten problem wprowadzono możliwość programowania wielowątkowego. Jeśli dłuższy kod przetwarzający dane umieścimy w metodzie stworzonej przez programistę o przykładowej nazwie LongWork(), postaci:

public void LongWork(object param)
{
 // jakieś dłuższe przetwarzanie
}

to możemy ją z przykładowej akcji RunLongWork() wywołać następująco:

public object RunLongWork()
{
  RunAsync(LongWork);
}

Funkcja RunAsync() zleca serwerowi zakolejkowanie i wykonanie metody LongWork() asynchronicznie w osobnym wątku. Sama funkcja RunAsync() trwa jedynie chwilę, a więc akcja RunLongWork() kończy się bardzo szybko.

Cykliczne odświeżanie sesji klienckiej

Jeśli metoda LongWork() generuje komunikaty do klienta (np. zmienia wygląd okien poprzez przypisywanie wartości do parametrów) to komunikaty te zostaną wysłane dopiero wtedy, gdy wątek wykonujący LongWork() się zakończy. W przypadku gdyby długie przetwarzanie było podzielone na fazy, możemy co jakiś czas kazać klientowi się odświeżyć, poprzez wykonanie funkcji RefreshClient(). Kod może wyglądać następująco:

public void LongWork(object param)
{
  // jakieś dłuższe przetwarzanie
  RefreshClient();
  // dalsze długie przetwarzanie
}

Funkcja RefreshClient() działa poprawnie jedynie wtedy, gdy zostanie wywołana z osobnego wątku powołanego przez RunAsync. Nie działa, jeśli jest wywoływana z głównego wątku sesji. Jest tak, ponieważ główny wątek sesji w serwerze Neos musi być wolny, aby mógł przetwarzać komunikaty przesyłane między serwerem Neos a klientem.

Czasowe blokowanie sesji klienckiej

Dodatkowo często chcemy na czas przetwarzania nie dopuścić do wykonania przez użytkownika innych akcji, ale nie tak aby całkowicie zamrozić interfejs użytkownika. Chcemy pozwolić na chodzenie po gridzie albo na całkowite zamknięcie okna, jeśli operator nie ma ochoty czekać na koniec ptrzetwarzania. W takim przypadku należy wywołać funkcję LockClient() w celu zablokowania okna po stronie klienta, a na końcu funkcję UnlockClient(). Funkcję LockClient() wywołujemy w akcji inicjującej przetwarzanie, a UnlockClient() na końcu samego przetwarzania. Dobrym zwyczajem jest też poinformowanie na ekranie, że jakieś przetwarzanie ma miejsce. Najlepiej zdefiniować w obiekcie parametr np. o nazwie _PRZETWARZANIE, i od jego wartości uzależnić etykietę akcji, przycisku albo jakiegoś innego elementu okna. Wspomniane przykładowe metody powinny mieć następującą postać:

public void LongWork(object param)
{
  // jakieś dłuższe przetwarzanie
  RefreshClient();
  // dalsze długie przetwarzanie
  _PRZETWARZANIE = 0;
  UnlockClient();
}
public object RunLongWork()
{
  LockClient();
  _PRZETWARZANIE = 1;
  RunAsync(LongWork);
}