apartamenty jednowątkowe

Warning

Ryzyko zakleszczenia z async/await i blokowanie wywołań dla wątków STA: W kodzie zarządzanym (.NET) pętla komunikatów STA ma kluczowe znaczenie dla wysyłania wywołań COM. Blokowanie wątku STA za pomocą Task.Wait(), Task.Result, Thread.Sleep() lub ManualResetEvent.WaitOne() uniemożliwia zakończenie wywołań zwrotnych COM i wywołań międzyapartmentowych — co prowadzi do zakleszczenia.

// ❌ DEADLOCK — blocks the STA message loop
[STAThread]
static void Main()
{
    var result = GetDataAsync().Result; // Deadlock: awaited continuation
                                        // can't post back to this STA thread
}

// ✅ Correct — use async entry point or pump messages
[STAThread]
static async Task Main()
{
    var result = await GetDataAsync(); // continuation resumes on STA via SynchronizationContext
}

Jeśli nie możesz użyć await, użyj CoWaitForMultipleHandles (natywnie) albo ramki dyspozytora lub pętli komunikatów (w kodzie zarządzanym) zamiast niskopoziomowych mechanizmów oczekiwania w wątkach STA.

Korzystanie z jednowątkowych mieszkań (proces modelu mieszkania) oferuje model oparty na komunikatach do radzenia sobie z wieloma obiektami uruchomionymi jednocześnie. Umożliwia pisanie bardziej wydajnego kodu, umożliwiając wątkowi oczekiwanie na ukończenie pewnej czasochłonnej operacji w celu umożliwienia wykonania innego wątku.

Każdy wątek w procesie, który jest inicjowany jako proces modelu mieszkania, i który pobiera i wysyła komunikaty okien, jest wątkiem apartamentu jednowątkowego. Każdy wątek działa we własnym apartamencie. W obrębie apartamentu wskaźniki interfejsu mogą być przekazywane bez marshalingu, dlatego wszystkie obiekty w jednowątkowym apartamencie komunikują się bezpośrednio.

Logiczne grupowanie powiązanych obiektów, które wszystkie działają w tym samym wątku i dlatego muszą być wykonywane synchronicznie, może znajdować się w tym samym wątku jednowątkowego apartamentu. Jednak obiekt modelu mieszkania nie może znajdować się w więcej niż jednym wątku. Wywołania obiektów w innych wątkach muszą być wykonywane w kontekście wątku będącego ich właścicielem, więc rozproszony model COM automatycznie przełącza wątki podczas wywoływania metod za pośrednictwem serwera proxy.

Modele międzyprocesowe i międzywątne są podobne. Jeśli konieczne jest przekazanie wskaźnika interfejsu do obiektu w innym mieszkaniu (w innym wątku) w ramach tego samego procesu, należy użyć tego samego modelu marshalingu, który obiekty w różnych procesach używają do przekazywania wskaźników przez granice procesu. Uzyskując wskaźnik do standardowego obiektu marshalingu, można marshalować wskaźniki interfejsu między wątkami (między apartmentami) w taki sam sposób, jak między procesami. (Wskaźniki interfejsu muszą być marshalowane podczas przekazywania między apartamentami.)

Zasady dotyczące mieszkań jednowątkowych są proste, ale ważne jest, aby je uważnie przestrzegać:

  • Każdy obiekt powinien działać tylko w jednym wątku (w obrębie apartamentu jednowątkowego).
  • Zainicjuj bibliotekę COM dla każdego wątku.
  • Marshaluj wszystkie wskaźniki do obiektów podczas przekazywania ich między apartamentami.
  • Każdy jednowątkowy apartament musi mieć pętlę komunikatów do obsługi wywołań z innych procesów i apartamentów w ramach tego samego procesu. Pojedyncze wątkowe apartamenty bez obiektów (tylko klient) wymagają również pętli komunikatów do wysyłania komunikatów rozgłaszanych, z których korzystają niektóre aplikacje.
  • Obiekty oparte na bibliotekach DLL lub obiekty w procesie nie wywołują funkcji inicjowania COM; zamiast tego rejestrują swój model wątkowości w nazwanej wartości ThreadingModel pod kluczem InprocServer32 w rejestrze. Obiekty obsługujące mieszkanie muszą również uważnie zapisywać punkty wejścia bibliotek DLL. Istnieją specjalne zagadnienia dotyczące wątkowania serwerów przetwarzania. Aby uzyskać więcej informacji, zobacz In-Process Server Threading Issues.

Chociaż wiele obiektów może znajdować się w obrębie jednego wątku, żaden obiekt modelu apartamentowego nie może należeć do więcej niż jednego wątku.

Każdy wątek procesu klienta lub serwera spoza procesu musi wywołać CoInitialize lub wywołać CoInitializeEx i określić wartość parametru dwCoInit jako COINIT_APARTMENTTHREADED. Główny apartament to wątek, który jako pierwszy wywołuje CoInitializeEx. Aby uzyskać informacje na temat serwerów przetwarzania, zobacz In-Process Server Threading Issues.

Wszystkie wywołania do obiektu muszą być wykonywane w jego wątku (w jego apartamencie). Zabronione jest wywoływanie obiektu bezpośrednio z innego wątku; używanie obiektów w ten sposób bezwątkowy może powodować problemy z aplikacjami. Konsekwencją tej reguły jest to, że wszystkie wskaźniki do obiektów muszą być marshalowane przy przekazywaniu między apartamentami. Com udostępnia następujące dwie funkcje w tym celu:

Te funkcje opakowują wywołania coMarshalInterface i funkcji CoUnmarshalInterface, które wymagają użycia flagi MSHCTX_INPROC.

Zasadniczo marshalowanie odbywa się automatycznie przez COM. Na przykład podczas przekazywania wskaźnika interfejsu jako parametru w wywołaniu metody na serwerze proxy obiektu w innym apartamencie lub podczas wywoływania CoCreateInstance COM automatycznie przeprowadza marshaling. Jednak w niektórych szczególnych przypadkach, gdy autor aplikacji przekazuje wskaźniki interfejsu między apartamentami bez użycia standardowych mechanizmów COM, musi on ręcznie obsłużyć marshalowanie.

Jeśli jedno mieszkanie (apartament 1) w procesie ma wskaźnik interfejsu, a inne mieszkanie (Apartament 2) wymaga jego użycia, apartament 1 musi wywołać CoMarshalInterThreadInterfaceInStream do marshalingu interfejsu. Strumień utworzony przez tę funkcję jest bezpieczny dla wątków i musi być zapisany w zmiennej dostępnej dla Apartment 2. Apartament 2 musi przekazać ten strumień do CoGetInterfaceAndReleaseStream, aby usunąć interfejs i wrócić wskaźnik do serwera proxy, za pomocą którego może uzyskać dostęp do interfejsu. Główny apartament musi pozostać aktywny, dopóki klient nie zakończy wszystkich operacji COM (ponieważ niektóre obiekty wewnątrzprocesowe są ładowane w głównym apartamencie, jak opisano w In-Process Server Threading Issues). Po przekazaniu jednego obiektu między wątkami w ten sposób bardzo łatwo jest przekazać wskaźniki interfejsu jako parametry. To sprawia, że rozproszony model COM wykonuje obsługę marshalingu i przełączanie wątków na potrzeby aplikacji.

Aby obsługiwać wywołania pochodzące z innych procesów i apartamentów w ramach tego samego procesu, każdy jednowątkowy apartament musi mieć pętlę komunikatów. Oznacza to, że funkcja robocza wątku musi mieć pętlę GetMessage/DispatchMessage. Jeśli inne elementy pierwotne synchronizacji są używane do komunikacji między wątkami, funkcja MsgWaitForMultipleObjects może służyć do oczekiwania zarówno dla komunikatów, jak i zdarzeń synchronizacji wątków. Dokumentacja tej funkcji zawiera przykład pętli tego rodzaju, generującej kombinacje.

COM tworzy ukryte okno za pomocą klasy systemu Windows „OleMainThreadWndClass” w każdym jednowątkowym apartamencie. Wywołanie obiektu jest odbierane jako komunikat wysyłany do tego ukrytego okna. Gdy apartament obiektu odbiera i wysyła komunikat, ukryte okno go otrzyma. Następnie procedura okna wywoła odpowiednią metodę interfejsu obiektu.

Gdy wielu klientów wywołuje obiekt, wywołania są umieszczane w kolejce komunikatów, a obiekt otrzymuje wywołanie za każdym razem, gdy jego apartment pobiera i przetwarza komunikaty. Ponieważ wywołania są synchronizowane przez COM i zawsze obsługiwane przez wątek należący do apartamentu obiektu, implementacje interfejsów obiektu nie muszą zapewniać synchronizacji. Jednowątkowe apartamenty mogą implementować interfejs IMessageFilter, aby w razie potrzeby umożliwić anulowanie wywołań lub odbieranie komunikatów okiennych.

Może dojść do ponownego wejścia do obiektu, jeśli implementacja jednej z jego metod interfejsu odbiera i wysyła komunikaty lub wykonuje wywołanie ORPC do innego wątku, powodując tym samym dostarczenie do obiektu kolejnego wywołania (w tym samym apartamencie). OLE nie zapobiega ponownemu wejściu w tym samym wątku, ale może pomóc zapewnić bezpieczeństwo wielowątkowe. Jest to identyczne z sytuacją, w której można ponownie wejść do procedury okna, jeśli podczas przetwarzania komunikatu pobiera ona i przekazuje komunikaty. Jednak wywołanie jednowątkowego serwera apartamentu uruchomionego poza procesem, który wywołuje inny jednowątkowy serwer apartamentu, umożliwi ponowne wejście do pierwszego serwera.

uzyskiwanie dostępu do interfejsów w apartamentach

wybieranie modelu wątkowego

wielowątkowe apartamenty

problemy z wątkami serwera In-Process

procesy, wątki i apartamenty

Komunikacja jednowątkowa i wielowątkowa