Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Warning
Risco de deadlock com async/await e chamadas de bloqueio em threads STA: No código gerido (.NET), o ciclo de mensagens STA é crítico para o despacho de chamadas COM. Bloquear uma thread STA com Task.Wait(), Task.Result, Thread.Sleep() ou ManualResetEvent.WaitOne() impede que chamadas de retorno COM e chamadas entre apartamentos sejam concluídas — causando um impasse.
// ❌ 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
}
Se não conseguir utilizar await, utilize CoWaitForMultipleHandles (nativo) ou um frame de dispatcher/loop de mensagens (gerido) em vez de primitivas de espera de baixo nível em threads STA.
A utilização de apartamentos monothread (o modelo de apartamento) oferece um paradigma baseado em mensagens para lidar com vários objetos executados concorrentemente. Ele permite que você escreva código mais eficiente, permitindo que um thread, enquanto aguarda a conclusão de alguma operação demorada, permita que outro thread seja executado.
Cada thread num processo inicializado segundo o modelo apartment, e que obtém e distribui mensagens de janela, é uma thread apartment de execução única. Cada fio vive dentro do seu próprio apartamento. Num apartamento, os ponteiros de interface podem ser passados sem encapsulamento e, portanto, todos os objetos num thread de apartamento monothread comunicam diretamente entre si.
Um agrupamento lógico de objetos relacionados que todos são executados no mesmo thread e, portanto, devem ter execução síncrona, pode viver no mesmo thread de apartamento de thread único. No entanto, um objeto de modelo de apartamento não pode residir em mais de um thread. As chamadas para objetos em outros threads devem ser feitas dentro do contexto do thread proprietário, portanto, o COM distribuído alterna threads automaticamente quando você chama um proxy.
Os modelos interprocesso e interthread são semelhantes. Quando é necessário passar um ponteiro de interface para um objeto noutro apartment (noutra linha de execução) dentro do mesmo processo, utiliza-se o mesmo modelo de marshaling que os objetos em processos diferentes utilizam para passar ponteiros através das fronteiras entre processos. Ao obter um ponteiro para o objeto de marshaling padrão, pode efetuar o marshaling de ponteiros de interface através de limites entre threads (entre apartments) da mesma forma que o faz entre processos. (Os ponteiros de interface devem ser serializados quando são passados entre diferentes apartamentos.)
As regras para unidades monothread são simples, mas é importante segui-las com atenção:
- Cada objeto deve viver em apenas um fio (dentro de um apartamento de rosca única).
- Inicialize a biblioteca COM para cada thread.
- Efetue o marshaling de todos os ponteiros para objetos ao passá-los entre apartments.
- Cada apartamento monofio deve ter um ciclo de mensagens para processar chamadas de outros processos e de apartamentos dentro desse mesmo processo. Os apartamentos monothread sem objetos (apenas cliente) também precisam de um ciclo de mensagens para processar as mensagens de difusão utilizadas por algumas aplicações.
- Objetos baseados em DLL ou no processo não chamam as funções de inicialização do COM; em vez disso, registam o seu modelo de threading sob o valor com nome ThreadingModel na chave InprocServer32 no registo. Objetos com reconhecimento de apartamento também devem gravar pontos de entrada DLL com cuidado. Existem considerações especiais que se aplicam à utilização de threads em servidores em processo. Para obter mais informações, consulte In-Process Server Threading Issues.
Embora vários objetos possam existir num único thread, nenhum objeto do modelo apartment pode existir em mais do que um thread.
Cada thread de um processo cliente ou servidor fora do processo deve chamar CoInitializeou chamar CoInitializeEx e especificar COINIT_APARTMENTTHREADED para o parâmetro dwCoInit. O apartamento principal é o tópico que chama CoInitializeEx primeiro. Para obter informações sobre servidores em processo, consulte In-Process Server Threading Issues.
Todas as chamadas para um objeto devem ser feitas em seu thread (dentro de seu apartamento). É proibido chamar diretamente um objeto a partir de outra thread; a utilização de objetos desta forma multithread pode causar problemas nas aplicações. A implicação desta regra é que todos os ponteiros para objetos devem ser serializados ao serem passados entre apartments. Para o efeito, a COM prevê as duas funções seguintes:
- CoMarshalInterThreadInterfaceInStream organiza uma interface para um objeto de fluxo que é devolvido ao chamador.
- CoGetInterfaceAndReleaseStream desagrega um ponteiro de interface de um objeto de fluxo de dados e liberta-o.
Estas funções encapsulam chamadas às funções CoMarshalInterface e CoUnmarshalInterface, que exigem a utilização do sinalizador MSHCTX_INPROC.
Em geral, o empacotamento é realizado automaticamente pelo próprio COM. Por exemplo, ao passar um ponteiro de interface como um parâmetro em uma chamada de método em um proxy para um objeto em outro apartamento, ou ao chamar CoCreateInstance, o COM faz o marshaling automaticamente. No entanto, em alguns casos especiais, nos quais o programador da aplicação passa ponteiros de interface entre "apartments" sem utilizar os mecanismos COM normais, tem de efetuar o marshaling manualmente.
Se um apartamento (Apartamento 1) em um processo tem um ponteiro de interface e outro apartamento (Apartamento 2) requer seu uso, o Apartamento 1 deve chamar CoMarshalInterThreadInterfaceInStream para organizar a interface. O stream criado por esta função é seguro para threads e tem de ser armazenado numa variável acessível pelo Apartment 2. O Apartment 2 tem de passar este stream para CoGetInterfaceAndReleaseStream para fazer o unmarshal da interface e receberá em troca um ponteiro para um proxy através do qual pode aceder à interface. O apartamento principal deve permanecer vivo até que o cliente tenha concluído todo o trabalho COM (porque alguns objetos em processo são carregados no apartamento principal, conforme descrito em In-Process Server Threading Issues). Depois que um objeto foi passado entre threads dessa maneira, é muito fácil passar ponteiros de interface como parâmetros. Dessa forma, o COM distribuído faz a serialização e a alternância entre threads da aplicação.
Para lidar com chamadas de outros processos e apartamentos dentro do mesmo processo, cada apartamento de thread único deve ter um loop de mensagens. Isso significa que a função de trabalho do thread deve ter um loop GetMessage/DispatchMessage. Se outras primitivas de sincronização estiverem sendo usadas para se comunicar entre threads, a funçãoMsgWaitForMultipleObjects poderá ser usada para aguardar mensagens e eventos de sincronização de threads. A documentação para esta função tem um exemplo deste tipo de loop de combinação.
O COM cria uma janela oculta usando a classe do Windows "OleMainThreadWndClass" em cada apartamento monofio. Uma chamada para um objeto é recebida como uma mensagem de janela para essa janela oculta. Quando o apartamento do objeto recupera e envia a mensagem, a janela oculta a receberá. O procedimento da janela chamará em seguida o método correspondente da interface do objeto.
Quando vários clientes invocam um objeto, as chamadas são colocadas em fila na fila de mensagens e o objeto receberá uma chamada sempre que o respetivo apartamento recuperar e processar mensagens. Como as chamadas são sincronizadas por COM e as chamadas são sempre entregues pelo thread que pertence ao apartamento do objeto, as implementações de interface do objeto não precisam fornecer sincronização. Os apartamentos de thread única podem implementar IMessageFilter para lhes permitir cancelar chamadas ou receber mensagens de janela quando necessário.
O objeto pode ser reinserido se uma de suas implementações de método de interface recuperar e despachar mensagens ou fizer uma chamada ORPC para outro thread, fazendo com que outra chamada seja entregue ao objeto (pelo mesmo apartamento). OLE não impede a reentrada na mesma cadeia de execução, mas pode ajudar a garantir segurança entre threads. Isso é idêntico à maneira pela qual um procedimento de janela pode ser reinserido se recuperar e enviar mensagens durante o processamento de uma mensagem. No entanto, chamar um servidor de apartamento de thread único fora de processo que chama outro servidor de apartamento de thread único permitirá que o primeiro servidor seja reinserido.
Tópicos relacionados