[PL] Multithreading safe - jak to robi .NET Framework?

Przy pisaniu swojej wersji Wiki-pajączka uwziąłem się, aby znów wrócić do tematu nazwijmy to na razie w cudzysłowiu "optymalizacji pod multi-core". Pod tą optymalizacją oczywiście na razie nie kryję się nic innego jak wielowątkowość.

Uczestnicy zabawy w tym temacie skutecznie upraszczali sobie życie podarunkiem w postaci BackgroundWorker'a. Ja postanowiłem czysto po wątkach zrobić mechanizm, który by jeszcze synchronizował działanie aktualizacji cache'a, statystyk oraz bieżące przeszukiwanie tego co już jest nagromadzone.

W między czasie padł problem synchronizacji wątków i wykorzystywania wspólnych zasobów w sposób bezpieczny w konteście. Tego typu problematykę łatwo spotkać gdy bawimy się Forms'ami i z punktu innego wątku próbujemy zmienić coś w kontrolkach formularza.

Wyskakuje wyjątek i trzeba się niestety pobawić w stworzenie delegatu, sprawdzenie Control.InvokeRequired oraz dalej wykonywać metodę Control.Invoke, która uruchamia funkcje w kontekście wątku gdzie kontrolka została stworzona.

Przykład z mojego jeszcze bałaganiarskiego kodu, który jak posprzątam to w końcu opublikuję:

        #region Multithreading safe UI update routinues
        //delegates
        delegate void updateUI_VoidUpdateCallback();
        //routinues
        private void updateUI_CacheUpdate()
        {
            if (labelLoading.InvokeRequired)
            {
                updateUI_VoidUpdateCallback d = new updateUI_VoidUpdateCallback(updateUI_CacheUpdate);
                this.Invoke(d, null);
            }
            else
            {
                labelLoading.Text = "Wait.. Web [" + countLoad + "] Cache [" + countUse + "]";
                labelLoading.Update();
            }
        }
        #endregion

Zastanawiałem się jak taki mechanizm sobie samemu zrobić. Control nie ma tej funkcjonalności wydzielonej w jakiejś abstrakcyjnej formie, którą można by podpiąć, w sumie i słusznie bo tam zasoby są bardzo konkretnie zdefiniowane, a przynajmniej powinny.

Jedyne o czym Control informuje to fakt, że implementuje interfejs ISynchronizeInvoke zawierający prototypy wszystkich metod związanych z inwokacją :) Super..

Miałem swoje podejrzenia, które zacząłem w swoim bardziej abstrakcyjnym mechanizmie pisać. Podejrzewałem, że Control sprawdza zasobnik z handlerem (tam pewnie HWND) a potem poprzez odpowiednie wiadomości w głównej petli lub system (eventy) przekazuje informacje do odpowiedniego wątku, że ma pobrać pointer do delegatu z funkcją i ją wykonać w swoim kontekście. W swojej wersji też chciałem stworzyć jakiś pojemniczek, który potem można by sobie opakowywać a potem dogrzebać się do SendMessage()/PostMessage() .

Jak wiadomo źródła .NET Framework już sobie podejrzeć można. Co też uczyniłem i sprawdziłem swoje powyższe podejrzenia.

Tak wygląda InvokeRequired w tej klasie Control:

public bool InvokeRequired {
   get {
     using (new MultithreadSafeCallScope())
     {
       HandleRef hwnd;
       if (IsHandleCreated) {
           hwnd = new HandleRef(this, Handle);
       }
       else {
          Control marshalingControl = FindMarshalingControl(); 
          if (!marshalingControl.IsHandleCreated) {
                  return false;
          }

    hwnd = new HandleRef(
marshalingControl,
marshalingControl.Handle);
  }

  int pid;
  int hwndThread =
     SafeNativeMethods.GetWindowThreadProcessId(hwnd, out pid);

  int currentThread = SafeNativeMethods.GetCurrentThreadId();

return(hwndThread != currentThread);
}}}

Jak nie patrzeć testowane jest HWND i wątek, do którego jest natywnie przypisany.
A teraz jak wygląda Invoke? Powiem tak, krótko :)

        public Object Invoke(Delegate method, params Object[] args) {
            using (new MultithreadSafeCallScope()) {
                Control marshaler = FindMarshalingControl();
                return marshaler.MarshaledInvoke(this, method, args, true);
            }
        }

A bez żartów, przechodząc przez całe ciało ::MarchaledInvoke gdzie sprawdzanych jest mnóstwo warunków, rekonstruowany jest kontekst wykonania w pewnym momencie wykonywany jest kod:

if (syncSameThread) {
   InvokeMarshaledCallbacks();
} else {
    //
   UnsafeNativeMethods.PostMessage(
new HandleRef(this, Handle),
threadCallbackMessage,
IntPtr.Zero,
IntPtr.Zero);
}

Czyli mniej więcej podejrzenia się zgodziły. Analizę asynchronicznego wykonania zostawiam jako pracę domową :)

Technorati Tagi: Polish Posts,coding,.NET Framework,geeks,multicore