[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ą 🙂

Comments (0)

Skip to main content