ApartamentStan dla opornych

120

Właśnie poprawiłem błąd, używając tego:

_Thread.SetApartmentState(ApartmentState.STA);

Teraz chciałbym zrozumieć, co to oznacza i dlaczego działa!

Benjol
źródło
1
Ten post może ci pomóc
Arsen Mkrtchyan

Odpowiedzi:

236

COM jest dziadkiem .NET. Mieli z tym dość wzniosłe cele, jedną z rzeczy, którą robi COM, ale całkowicie pomija .NET, jest zapewnienie gwarancji wątków dla klasy. Klasa COM może publikować, jakie ma wymagania dotyczące wątków. A infrastruktura COM zapewnia spełnienie tych wymagań.

Jest to całkowicie nieobecne w .NET. Możesz użyć obiektu Queue <> na przykład w wielu wątkach, ale jeśli nie zablokujesz się prawidłowo, będziesz miał paskudny błąd w kodzie, który jest bardzo trudny do zdiagnozowania.

Dokładne szczegóły wątku COM są zbyt duże, aby zmieścić się w poście. Skoncentruję się na szczegółach twojego pytania. Wątek, który tworzy obiekty COM, musi poinformować COM, jakiego rodzaju wsparcie chce udzielić klasom COM, które mają ograniczone opcje wątkowości. Zdecydowana większość tych klas obsługuje tylko tak zwane wątki Apartment, ich metody interfejsu można bezpiecznie wywołać tylko z tego samego wątku, który utworzył instancję. Innymi słowy, ogłaszają: „W ogóle nie obsługuję wątków, uważaj, aby nigdy nie dzwonić do mnie z niewłaściwego wątku”. Nawet jeśli kod klient rzeczywiście ma zadzwonić z innego wątku.

Istnieją dwa rodzaje, STA (mieszkanie z pojedynczym gwintem) i MTA. Jest ona określona w wywołaniu CoInitializeEx (), funkcji, która musi zostać wywołana przez każdy wątek, który robi cokolwiek z COM. CLR wykonuje to wywołanie automatycznie za każdym razem, gdy rozpoczyna wątek. W przypadku głównego wątku startowego twojego programu pobiera wartość do przekazania z atrybutu [STAThread] lub [MTAThread] w metodzie Main (). Domyślnie jest to MTA. W przypadku wątków tworzonych samodzielnie jest to określane przez wywołanie metody SetApartmentState (). Domyślnie jest to MTA. Wątki Threadpool są zawsze MTA i nie można ich zmienić.

W systemie Windows jest dużo kodu, który wymaga STA. Godne uwagi przykłady, których sam użyłbyś, to Schowek, Przeciągnij + Upuść i okna dialogowe powłoki (takie jak OpenFileDialog). I dużo kodu, którego nie widać, na przykład programy automatyzacji interfejsu użytkownika i punkty zaczepienia do obserwowania komunikatów. Żaden z tych kodów nie musi być bezpieczny dla wątków, jego autorowi byłoby bardzo trudno uczynić go bezpiecznym, nie wiedząc, w którym programie jest używany. W związku z tym wątek interfejsu użytkownika projektu WPF lub Windows Forms musi zawsze być STA, aby obsługiwał taki kod, podobnie jak każdy wątek, który tworzy okno.

Obietnica, którą złożysz COM, że twój wątek jest STA , wymaga jednak przestrzegania umowy dotyczącej mieszkania jednowątkowego. Są dość sztywne i możesz być trudny do zdiagnozowania problemów po zerwaniu kontraktu. Wymagania są takie, aby nigdy nie blokować wątku przez dłuższy czas i pompować pętlę komunikatów. Ten ostatni wymóg jest spełniony przez wątek UI WPF lub Winforms, ale będziesz musiał sam się tym zająć, jeśli utworzysz własny wątek STA. Typową diagnostyką zerwania kontraktu jest impas.

Przy okazji jest sporo wsparcia wbudowanego w CLR, które obsługuje te wymagania, pomagając uniknąć kłopotów. Instrukcja lock i metody WaitOne () pompują pętlę komunikatów, gdy są one blokowane w wątku STA. To jednak zajmuje się tylko wymogiem nigdy nie blokowania, nadal musisz utworzyć własną pętlę komunikatów. Application.Run () w WPF i Winforms.

Wcześniej dostarczyłem odpowiedź, która zawiera więcej szczegółów na temat znaczenia pętli komunikatów dla zadowolenia COM. Znajdziesz wpis tutaj .

Hans Passant
źródło
4
Doskonała odpowiedź! Błąd, który rozwiązałem, dotyczył wątku, który utworzyłem dla długo działającego narzędzia do tworzenia raportów, który używał kontrolek WPF do tworzenia części raportu, więc ma to sens, chociaż nie jestem świadomy, że ten wątek ma pętlę komunikatów .
Benjol
4
Poważnie musiałem kilkakrotnie przeczytać post MSDN, aby go zrozumieć, Twoja odpowiedź jest bardzo jasna i dobrze napisana. Dziękuję Ci!
ak3nat0n