ApartmentState para manequins

119

Acabei de corrigir um bug usando isto:

_Thread.SetApartmentState(ApartmentState.STA);

Agora, gostaria de entender o que significa e por que funciona!

Benjol
fonte
1
Esta postagem pode ajudá-lo
Arsen Mkrtchyan

Respostas:

235

COM é o avô do .NET. Eles tinham objetivos muito elevados, uma das coisas que o COM faz, mas o .NET ignora completamente, é fornecer garantias de threading para uma classe. Uma classe COM pode publicar que tipo de requisitos de threading ela possui. E a infraestrutura COM garante que esses requisitos sejam atendidos.

Isso está completamente ausente no .NET. Você pode usar um objeto Queue <>, por exemplo, em vários threads, mas se você não bloquear corretamente, terá um bug desagradável em seu código que é muito difícil de diagnosticar.

Os detalhes exatos do segmento COM são muito grandes para caber em uma postagem. Vou me concentrar nos detalhes de sua pergunta. Um thread que cria objetos COM deve informar ao COM que tipo de suporte deseja fornecer às classes COM que têm opções de threading restritas. A grande maioria dessas classes suporta apenas o chamado thread de apartamento, seus métodos de interface só podem ser chamados com segurança a partir do mesmo thread que criou a instância. Em outras palavras, eles anunciam "Eu não apóio nenhum tópico de discussão , por favor, tome cuidado para nunca me ligar do tópico errado". Mesmo se o código do cliente realmente o chamar de outro thread.

Existem dois tipos, STA (Single Threaded Apartment) e MTA. Ele é especificado na chamada CoInitializeEx (), uma função que deve ser chamada por qualquer thread que faça qualquer coisa com COM. O CLR faz essa chamada automaticamente sempre que inicia um thread. Para o thread de inicialização principal de seu programa, ele obtém o valor a ser passado do atributo [STAThread] ou [MTAThread] em seu método Main (). O padrão é MTA. Para threads que você mesmo cria, é determinado por sua chamada para SetApartmentState (). O padrão é MTA. Threads Threadpool são sempre MTA, que não podem ser alterados.

Existem muitos códigos no Windows que requerem um STA. Exemplos notáveis ​​que você usaria são a área de transferência, arrastar e soltar e os diálogos do shell (como OpenFileDialog). E muito código que você não pode ver, como programas de UI Automation e ganchos para observar mensagens. Nenhum desses códigos precisa ser thread-safe, seu autor teria muita dificuldade em torná-lo seguro sem saber em qual programa ele é usado. Da mesma forma, o thread de interface do usuário de um projeto WPF ou Windows Forms deve sempre ser STA para oferecer suporte a esse código, como qualquer thread que cria uma janela.

A promessa que você faz ao COM de que seu thread é STA, entretanto , exige que você siga o contrato de apartamento de thread único. Eles são muito rígidos e pode ser complicado diagnosticar problemas quando quebra o contrato. Os requisitos são que você nunca bloqueie o encadeamento por qualquer período de tempo e que bombeie um loop de mensagem. O último requisito é atendido por um thread de IU do WPF ou Winforms, mas você mesmo precisará cuidar disso se criar seu próprio thread de STA. O diagnóstico comum para quebrar o contrato é o impasse.

Há bastante suporte integrado no CLR para dar suporte a esses requisitos, ajudando você a evitar problemas. A instrução de bloqueio e os métodos WaitOne () bombeiam um loop de mensagem quando ela é bloqueada em um encadeamento STA. No entanto, isso só cuida do requisito de nunca bloquear, você ainda precisa criar seu próprio loop de mensagem. Application.Run () no WPF e no WinForms.

Eu já enviei uma resposta que contém mais detalhes sobre a importância de ter um loop de mensagens para manter o COM feliz. Você encontrará a postagem aqui .

Hans Passant
fonte
4
Excelente resposta! O bug que resolvi foi para um thread que criei para um construtor de relatórios de longa execução que usava controles WPF para construir partes do relatório, o que faz sentido, embora eu não saiba que esse thread tem um loop de mensagens nele .
Benjol
4
Eu realmente tive que ler o post do MSDN várias vezes para entendê-lo. Sua resposta é muito clara e bem escrita. Obrigado!
ak3nat0n