Descrições e funcionamento de modelos de threading OLE
Este artigo descreve modelos de threading OLE.
Versão original do produto: modelos de rosqueamento OLE
Número original do KB: 150777
Resumo
Os objetos COM podem ser usados em vários threads de um processo. Os termos "STA (Single-threaded Apartment" e "MTA (Multi-threaded Apartment" são usados para criar uma estrutura conceitual para descrever a relação entre objetos e threads, as relações de simultaneidade entre objetos, os meios pelos quais as chamadas de método são entregues a um objeto e as regras para passar ponteiros de interface entre threads. Os componentes e seus clientes escolhem entre os dois modelos de apartamentos a seguir atualmente suportados pelo COM:
STA (modelo de apartamento de thread único): um ou mais threads em um processo usam COM e as chamadas para objetos COM são sincronizadas por COM. As interfaces são empacotadas entre threads. Um caso degenerado do modelo de apartamento de thread único, em que apenas um thread em um determinado processo usa COM, é chamado de modelo de thread único. Às vezes, os anteriores se referiam ao modelo STA simplesmente como o "modelo de apartamento".
MTA (modelo de apartamento multithreaded): um ou mais threads usam COM e as chamadas para objetos COM associados ao MTA são feitas diretamente por todos os threads associados ao MTA sem qualquer interposição de código do sistema entre o chamador e o objeto. Como vários clientes simultâneos podem estar chamando objetos mais ou menos simultaneamente (simultaneamente em sistemas com vários processadores), os objetos devem sincronizar seu estado interno por si mesmos. As interfaces não são empacotadas entre threads. Às vezes, os anteriores se referiam a esse modelo como o "modelo de thread livre".
Tanto o modelo STA quanto o modelo MTA podem ser usados no mesmo processo. Isso às vezes é chamado de processo de "modelo misto".
O MTA é introduzido no NT 4.0 e está disponível no Windows 95 com DCOM95. O modelo STA existe no Windows NT 3.51 e Windows 95, bem como no NT 4.0 e Windows 95 com DCOM95.
Visão geral
Os modelos de threading no COM fornecem o mecanismo para que os componentes que usam diferentes arquiteturas de threading trabalhem juntos. Eles também fornecem serviços de sincronização para componentes que os exigem. Por exemplo, um objeto específico pode ser projetado para ser chamado apenas por um único thread e pode não sincronizar chamadas simultâneas de clientes. Se esse objeto for chamado simultaneamente por vários threads, ele falhará ou causará erros. O COM fornece os mecanismos para lidar com essa interoperabilidade de arquiteturas de threading.
Mesmo os componentes com reconhecimento de thread geralmente precisam de serviços de sincronização. Por exemplo, os componentes que têm uma interface gráfica do usuário (GUI), como controles OLE/ActiveX, inserções ativas in-loco e documentos ActiveX, exigem sincronização e serialização de chamadas COM e mensagens de janela. O COM fornece esses serviços de sincronização para que esses componentes possam ser gravados sem código de sincronização complexo.
Um "apartamento" tem vários aspectos inter-relacionados. Primeiro, é uma construção lógica para pensar sobre simultaneidade, por exemplo, como os threads se relacionam com um conjunto de objetos COM. Em segundo lugar, é um conjunto de regras que os programadores devem obedecer para receber o comportamento de simultaneidade que esperam do ambiente COM. Por fim, é o código fornecido pelo sistema que ajuda os programadores a gerenciar a simultaneidade de thread em relação aos objetos COM.
O termo "apartamento" vem de uma metáfora na qual um processo é concebido como uma entidade discreta, como um "edifício" que é subdividido em um conjunto de "locais" relacionados, mas diferentes, chamados "apartamentos". Um apartamento é um "contêiner lógico" que cria uma associação entre objetos e, em alguns casos, threads. Os threads não são apartamentos, embora possa haver um único thread logicamente associado a um apartamento no modelo STA. Objetos não são apartamentos, embora cada objeto esteja associado a um e apenas um apartamento. Mas os apartamentos são mais do que apenas uma construção lógica; suas regras descrevem o comportamento do sistema COM. Se as regras dos modelos de apartamento não forem seguidas, os objetos COM não funcionarão corretamente.
Mais detalhes
Um STA (apartamento de thread único) é um conjunto de objetos COM associados a um thread específico. Esses objetos são associados ao apartamento sendo criados pelo thread ou, mais precisamente, sendo expostos pela primeira vez ao sistema COM (normalmente por marshaling) no thread. UM STA é considerado um lugar onde um objeto ou um proxy "vive". Se o objeto ou proxy precisar ser acessado por outro apartamento (no mesmo processo ou em um processo diferente), seu ponteiro de interface deverá ser empacotado para esse apartamento em que um novo proxy é criado. Se as regras do modelo de apartamento forem seguidas, nenhuma chamada direta de outros threads no mesmo processo será permitida nesse objeto; Isso violaria a regra de que todos os objetos dentro de um determinado apartamento são executados em um único thread. A regra existe porque a maioria dos códigos em execução em um STA não funcionará corretamente se for executada em threads adicionais.
O thread associado a um STA deve chamar CoInitialize
or CoInitializeEx(NULL, COINIT_APARTMENTTHREADED)
e deve recuperar e despachar mensagens de janela para que os objetos associados recebam chamadas de entrada. O COM despacha e sincroniza chamadas para objetos em um STA usando mensagens de janela, conforme descrito posteriormente neste artigo.
O "STA principal" é o thread que chama CoInitialize
ou CoInitializeEx(NULL, COINIT_APARTMENTTHREADED)
primeiro dentro de um determinado processo. O STA principal de um processo deve permanecer ativo até que todo o trabalho COM seja concluído porque alguns objetos no processo são sempre carregados no STA principal, conforme descrito posteriormente neste artigo.
O Windows NT 4.0 e o DCOM95 introduzem um novo tipo de apartamento chamado MTA (multi-threaded apartment). Um MTA é um conjunto de objetos COM associados a um conjunto de threads no processo, de modo que qualquer thread possa chamar qualquer implementação de objeto diretamente sem a interposição do código do sistema. Os ponteiros de interface para qualquer objeto no MTA podem ser passados entre os threads associados ao MTA sem precisar ser empacotados. Todos os threads no processo que chamam CoInitializeEx(NULL, COINIT_MULTITHREADED)
estão associados ao MTA. Ao contrário do STA descrito acima, os threads em um MTA não precisam recuperar e despachar mensagens de janela para que os objetos associados recebam chamadas de entrada. O COM não sincroniza chamadas para objetos em um MTA. Os objetos em um MTA devem proteger seu estado interno contra corrupção pela interação de vários threads simultâneos e não podem fazer suposições sobre o conteúdo do Armazenamento Local de Thread permanecer constante entre diferentes invocações de método.
Um processo pode ter qualquer número de STAs, mas, no máximo, pode ter um MTA. O MTA consiste em um ou mais threads. Os STAs têm um thread cada. Um fio pertence, no máximo, a um apartamento. Os objetos pertencem a um e apenas um apartamento. Os ponteiros de interface devem sempre ser empacotados entre apartamentos (embora o resultado do marshaling possa ser um ponteiro direto em vez de um proxy). Veja as informações abaixo em CoCreateFreeThreadedMarshaler
.
Um processo escolhe um dos modelos de threading fornecidos pelo COM. Um processo de modelo STA tem um ou mais STAs e não tem um MTA. Um processo de modelo MTA tem um MTA com um ou mais threads e não tem nenhum STA. Um processo de modelo misto tem um MTA e qualquer número de STAs.
Modelo de apartamento de thread único
O thread de um STA deve chamar CoInitialize
or CoInitializeEx(NULL, COINIT_APARTMENTTHREADED)
e deve recuperar e expedir mensagens de janela porque o COM usa mensagens de janela para sincronizar e expedir a entrega de chamadas para um objeto nesse modelo. Consulte a seção REFERÊNCIAS abaixo para obter mais informações.
Servidor que suporta o modelo STA:
No modelo STA, as chamadas para um objeto são sincronizadas por COM da mesma maneira que as mensagens de janela postadas em uma janela são sincronizadas. As chamadas são entregues usando mensagens de janela para o thread que criou o objeto. Consequentemente, o thread do objeto deve chamar Get
/PeekMessage
e DispatchMessage
receber chamadas. O COM cria uma janela oculta associada a cada STA. Uma chamada para um objeto de fora do STA é transferida pelo runtime COM para o thread do objeto usando uma mensagem de janela postada nessa janela oculta. Quando o thread associado ao STA do objeto recupera e despacha a mensagem, o procedimento de janela para a janela oculta, também implementado pelo COM, a recebe. O procedimento de janela é usado pelo runtime COM para "conectar" o thread associado ao STA porque o runtime COM está em ambos os lados da chamada do thread de propriedade COM para o thread do STA. O runtime COM (agora em execução no thread do STA) chama "up" por meio de um stub fornecido por COM no método de interface correspondente do objeto. O caminho de execução que retorna da chamada do método reverte a chamada "up"; a chamada retorna para o stub e o runtime COM, que passa o controle de volta para o thread de runtime COM por meio de uma mensagem de janela, que retorna pelo canal COM para o chamador original.
Quando vários clientes chamam um objeto STA, as chamadas são automaticamente enfileiradas na fila de mensagens pelo mecanismo de transferência de controle usado no STA. O objeto recebe uma chamada sempre que seu STA recupera e despacha mensagens. Como as chamadas são sincronizadas pelo COM dessa maneira e como as chamadas são sempre entregues no thread único associado ao STA do objeto, as implementações de interface do objeto não precisam fornecer sincronização.
Observação
O objeto pode ser inserido novamente se uma implementação de método de interface recuperar e despachar mensagens durante o processamento de uma chamada de método, fazendo com que outra chamada seja entregue ao objeto pelo mesmo STA. Uma maneira comum pela qual isso ocorre é se um objeto STA fizer uma chamada de saída (entre apartamentos/processos) usando COM. Isso é idêntico à maneira pela qual um procedimento de janela pode ser inserido novamente se recuperar e despachar mensagens durante o processamento de uma mensagem. O COM não impede a reentrada no mesmo thread, mas impede a execução simultânea. Ele também fornece um meio pelo qual a reentrada relacionada a COM pode ser gerenciada. Consulte a seção REFERÊNCIAS abaixo para obter mais informações. O objeto não será reinserido se as implementações de método não chamarem de seu apartamento ou recuperarem e despacharem mensagens.
Responsabilidades do cliente no modelo STA:
O código do cliente em execução em um processo e/ou thread que usa o modelo STA deve empacotar interfaces de um objeto entre apartamentos usando CoMarshalInterThreadInterfaceInStream
e CoGetInterfaceAndReleaseStream
. Por exemplo, se o Apartamento 1 no cliente tiver um ponteiro de interface e o Apartamento 2 exigir o uso dele, o Apartamento 1 deverá empacotar a interface usando CoMarshalInterThreadInterfaceInStream
. O objeto de fluxo retornado por essa função é thread-safe e seu ponteiro de interface deve ser armazenado em uma variável de memória direta acessível pelo Apartamento 2. O apartamento 2 deve passar essa interface de fluxo para CoGetInterfaceAndReleaseStream
desmarcar a interface no objeto subjacente e obter um ponteiro para um proxy por meio do qual ele pode acessar o objeto.
O apartamento principal de um determinado processo deve permanecer ativo até que o cliente tenha concluído todo o trabalho COM porque alguns objetos no processo são carregados no apartamento principal. (Mais informações são detalhadas abaixo).
Modelo de apartamento multi-thread
Um MTA é a coleção de objetos criados ou expostos por todos os threads no processo que chamaram CoInitializeEx(NULL, COINIT_MULTITHREADED)
o .
Observação
As implementações atuais do COM permitem que um thread que não inicializa explicitamente o COM faça parte do MTA. Um thread que não inicializa o COM faz parte do MTA somente se começar a usar o COM depois que pelo menos um outro thread no processo tiver chamado CoInitializeEx(NULL, COINIT_MULTITHREADED)
anteriormente . (É até possível que o próprio COM tenha inicializado o MTA quando nenhum thread do cliente o fez explicitamente; por exemplo, um thread associado a um STA chama CoGetClassObject
/CoCreateInstance[Ex]
em um CLSID marcado como "ThreadingModel=Free" e o COM cria implicitamente um MTA no qual o objeto de classe é carregado.) Consulte as informações sobre a interoperabilidade do modelo de threading abaixo.
No entanto, essa é uma configuração que pode causar problemas, como violações de acesso, em determinadas circunstâncias. Portanto, é recomendável que cada thread que precisa fazer o trabalho COM inicialize o COM chamando CoInitializeEx
e, em seguida, após a conclusão do trabalho COM, chame CoUninitialize
. O custo de inicializar "desnecessariamente" um MTA é mínimo.
Os threads MTA não precisam recuperar e expedir mensagens porque o COM não usa mensagens de janela nesse modelo para entregar chamadas a um objeto.
Servidor que suporta o modelo MTA:
No modelo MTA, as chamadas para um objeto não são sincronizadas por COM. Vários clientes podem chamar simultaneamente um objeto que dá suporte a esse modelo em threads diferentes, e o objeto deve fornecer sincronização em suas implementações de interface/método usando objetos de sincronização, como eventos, mutexes, semáforos etc. Os objetos MTA podem receber chamadas simultâneas de vários clientes fora do processo por meio de um pool de threads criados por COM pertencentes ao processo do objeto. Os objetos MTA podem receber chamadas simultâneas de vários clientes em processo em vários threads associados ao MTA.
Responsabilidades do cliente no modelo MTA:
O código do cliente em execução em um processo e/ou thread que usa o modelo MTA não precisa empacotar ponteiros de interface de um objeto entre ele e outros threads MTA. Em vez disso, um thread MTA pode usar um ponteiro de interface obtido de outro thread MTA como um ponteiro de memória direto. Quando um thread de cliente faz uma chamada para um objeto fora do processo, ele é suspenso até que a chamada seja concluída. As chamadas podem chegar em objetos associados ao MTA, enquanto todos os threads criados pelo aplicativo associados ao MTA são bloqueados em chamadas de saída. Nesse caso e em geral, as chamadas de entrada são entregues em threads fornecidos pelo runtime COM. Os filtros de mensagem (
IMessageFilter
) não estão disponíveis para uso no modelo MTA.
Modelos de threading misto
Um processo que dá suporte ao modelo de threading misto usará um MTA e um ou mais STAs. Os ponteiros de interface devem ser empacotados entre todos os apartamentos, mas podem ser usados sem empacotamento dentro do MTA. As chamadas para objetos em um STA são sincronizadas pelo COM para serem executadas em apenas um thread, enquanto as chamadas para objetos no MTA não são. No entanto, as chamadas de um STA para um MTA normalmente passam pelo código fornecido pelo sistema e alternam do thread STA para um thread MTA antes de serem entregues ao objeto.
Observação
Consulte a documentação do SDK e CoCreateFreeThreadedMarshaler()
a discussão dessa API abaixo para obter informações sobre casos em que ponteiros diretos podem ser usados e como um thread STA pode chamar diretamente um objeto associado pela primeira vez ao MTA e, vice-versa, de vários apartamentos.
Escolhendo os modelos de rosqueamento
Um componente pode optar por oferecer suporte ao modelo STA, ao modelo MTA ou a uma combinação dos dois usando o modelo de threading misto. Por exemplo, um objeto que faz E/S extensa pode optar por oferecer suporte ao MTA para fornecer resposta máxima aos clientes, permitindo que chamadas de interface sejam feitas durante a latência de E/S. Como alternativa, um objeto que interage com o usuário quase sempre opta por oferecer suporte ao STA para sincronizar chamadas COM de entrada com suas operações de GUI. O suporte ao modelo STA é mais fácil porque o COM fornece sincronização. O suporte ao modelo MTA é mais difícil porque o objeto deve implementar a sincronização, mas a resposta aos clientes é melhor porque a sincronização é usada para seções menores de código, em vez de para toda a chamada de interface, conforme fornecido pelo COM.
O modelo STA também é usado pelo Microsoft Transaction Server (MTS, anteriormente codinome "Viper") e, portanto, os objetos baseados em DLL que planejam ser executados no ambiente MTS devem usar o modelo STA. Os objetos implementados para o modelo MTA normalmente funcionarão bem em um ambiente MTS. No entanto, eles serão executados com menos eficiência porque usarão primitivos de sincronização de thread desnecessários.
Marcando o modelo de threading suportado de servidores In-Proc
Um thread usará o modelo MTA se chamar CoInitializeEx(NULL, COINIT_MULTITHREADED)
ou usar COM sem inicializá-lo. Um thread usará o modelo STA se chamar CoInitialize
ou CoInitializeEx(NULL, COINIT_APARTMENTTHREADED)
.
As CoInitialize
APIs fornecem controle de apartamento para o código do cliente e para objetos empacotados. EXEs, pois o código de inicialização do runtime COM pode inicializar o COM da maneira desejada.
No entanto, um servidor COM in-proc (baseado em DLL) não chama CoInitialize
/CoInitializeEx
porque essas APIs terão sido chamadas no momento em que o servidor DLL for carregado. Portanto, um servidor DLL deve usar o Registro para informar o COM sobre o modelo de threading compatível para que o COM possa garantir que o sistema opere de maneira compatível com ele. Um valor nomeado da chave CLSID\InprocServer32 do componente chamada ThreadingModel
é usado para essa finalidade da seguinte maneira:
ThreadingModel
value not present: dá suporte ao modelo de threading único.ThreadingModel=Apartment
: Suporta o modelo STA.ThreadingModel=Both
: Suporta o modelo STA e MTA.ThreadingModel=Free
: Suporta apenas MTA.
Observação
ThreadingModel
é um valor nomeado, não uma subchave de InprocServer32, conforme documentado incorretamente em algumas versões anteriores da documentação do Win32.
Os modelos de threading de servidores in-proc são discutidos posteriormente neste artigo. Se um servidor in-proc fornecer muitos tipos de objetos (cada um com seu próprio CLSID exclusivo), cada tipo poderá ter um valor diferente ThreadingModel
. Em outras palavras, o modelo de threading é por CLSID, não por pacote de código/DLL. No entanto, os pontos de entrada da API necessários para "inicializar" e consultar todos os servidores in-proc (DLLGetClassObject()
, DLLCanUnloadNow()
) devem ser thread-safe para qualquer servidor in-proc que dê suporte a vários threads (ou seja, um ThreadingModel
valor de Apartment, Both ou Free).
Como mencionado anteriormente, os servidores fora do processo não se marcam usando o valor ThreadingModel. Em vez disso, eles usam CoInitialize
ou CoInitializeEx
. Os servidores baseados em DLL que esperam ser executados fora do processo usando a funcionalidade "substituta" do COM (como o substituto fornecido pelo sistema DLLHOST.EXE) seguem as regras para servidores baseados em DLL; Não há considerações especiais nesse caso.
Quando o cliente e o objeto usam modelos de threading diferentes
A interação entre um cliente e um objeto fora do processo é direta, mesmo quando diferentes modelos de threading são usados, porque o cliente e o objeto estão em processos diferentes e o COM está envolvido na passagem de chamadas do cliente para o objeto. Como o COM é interposto entre o cliente e o servidor, ele fornece o código para interoperação dos modelos de threading. Por exemplo, se um objeto STA for chamado simultaneamente por vários clientes STA ou MTA, o COM sincronizará as chamadas colocando mensagens de janela correspondentes na fila de mensagens do servidor. O STA do objeto recebe uma chamada cada vez que recupera e despacha mensagens. Todas as combinações de interoperabilidade do modelo de threading são permitidas e totalmente suportadas entre clientes e objetos fora do processo.
A interação entre um cliente e um objeto no processo que usa diferentes modelos de threading é mais complicada. Embora o servidor esteja em proc, o COM deve se interpor entre o cliente e o objeto em alguns casos. Por exemplo, um objeto in-proc projetado para dar suporte ao modelo STA pode ser chamado simultaneamente por vários threads de um cliente. O COM não pode permitir que os threads do cliente acessem diretamente a interface do objeto porque o objeto não foi projetado para esse acesso simultâneo. Em vez disso, o COM deve garantir que as chamadas sejam sincronizadas e feitas somente pelo thread associado ao STA que "contém" o objeto. Apesar da complexidade adicional, todas as combinações de interoperabilidade do modelo de threading são permitidas entre clientes e objetos no processo.
Modelos de threading em servidores fora do processo (baseados em EXE)
A seguir estão três categorias de servidores fora do processo, cada um dos quais pode ser usado por qualquer cliente COM, independentemente do modelo de threading usado por esse cliente:
Servidor de modelo STA:
O servidor faz o trabalho COM em uma ou mais STAs. As chamadas de entrada são sincronizadas por COM e entregues pelo thread associado ao STA no qual o objeto foi criado. As chamadas de método de fábrica de classe são entregues pelo thread associado ao STA que registrou a fábrica de classes. O objeto e a fábrica de classes não precisam implementar a sincronização. No entanto, o implementador deve sincronizar o acesso a todas as variáveis globais usadas por vários STAs. O servidor deve usar
CoMarshalInterThreadInterfaceInStream
eCoGetInterfaceAndReleaseStream
empacotar ponteiros de interface, possivelmente de outros servidores, entre STAs. Opcionalmente, o servidor pode implementarIMessageFilter
em cada STA para controlar aspectos da entrega de chamadas por COM. Um caso degenerado é o servidor de modelo de thread único que faz o trabalho COM em um STA.Servidor Modelo MTA:
O servidor faz o trabalho COM em um ou mais threads, todos os quais pertencem ao MTA. As chamadas não são sincronizadas pelo COM. O COM cria um pool de threads no processo do servidor e uma chamada de cliente é entregue por qualquer um desses threads. Os threads não precisam recuperar e enviar mensagens. O objeto e a fábrica de classes devem implementar a sincronização. O servidor não precisa empacotar ponteiros de interface entre threads.
Servidor de modelo misto:
Consulte a seção neste artigo intitulada "Modelo de threading misto" para obter mais informações.
Modelos de threading em clientes
Existem três categorias de clientes:
Cliente do modelo STA:
O cliente faz o trabalho COM em threads associados a um ou mais STAs. O cliente deve usar
CoMarshalInterThreadInterfaceInStream
eCoGetInterfaceAndReleaseStream
empacotar ponteiros de interface entre STAs. Um caso degenerado é o cliente de modelo de thread único que faz o trabalho COM em um STA. O thread do cliente entra em um loop de mensagem fornecido pelo COM quando faz uma chamada de saída. O cliente pode usarIMessageFilter
para gerenciar retornos de chamada e processamento de mensagens de janela enquanto aguarda chamadas de saída e outros problemas de simultaneidade.Cliente modelo MTA:
O cliente faz o trabalho COM em um ou mais threads, todos os quais pertencem ao MTA. O cliente não precisa empacotar ponteiros de interface entre seus threads. O cliente não pode usar
IMessageFilter
o . Os threads do cliente são suspensos quando fazem uma chamada COM para um objeto fora do processo e retomam quando a chamada retorna. As chamadas de entrada chegam em threads criados e gerenciados por COM.Cliente de modelo misto:
Consulte a seção neste artigo intitulada "Modelo de threading misto" para obter mais informações.
Modelos de threading em servidores In-proc (baseados em DLL)
Há quatro categorias de servidores in-proc, cada uma das quais pode ser usada por qualquer cliente COM, independentemente do modelo de threading usado por esse cliente. No entanto, os servidores in-proc devem fornecer código de marshaling para qualquer interface personalizada (não definida pelo sistema) que implementarem se quiserem dar suporte à interoperabilidade do modelo de threading, pois isso normalmente requer que o COM mareche a interface entre os apartamentos do cliente. As quatro categorias são:
Servidor In-proc que suporta threading único (STA "principal") - sem
ThreadingModel
valor:Um objeto fornecido por esse servidor espera ser acessado pelo mesmo STA do cliente pelo qual foi criado. Além disso, o servidor espera que todos os seus pontos de entrada, como
DllGetClassObject
eDllCanUnloadNow
, e dados globais sejam acessados pelo mesmo thread (aquele associado ao STA principal). Os servidores que existiam antes da introdução do multi-threading no COM estão nesta categoria. Esses servidores não foram projetados para serem acessados por vários threads, portanto, o COM cria todos os objetos fornecidos pelo servidor no STA principal do processo e as chamadas para os objetos são entregues pelo thread associado ao STA principal. Outros apartamentos de clientes obtêm acesso ao objeto por meio de proxies. As chamadas dos outros apartamentos vão do proxy para o stub no STA principal (marshaling entre threads) e, em seguida, para o objeto. Esse marshaling permite que o COM sincronize chamadas para o objeto e as chamadas são entregues pelo STA no qual o objeto foi criado. O marshaling entre threads é lento em relação à chamada direta, portanto, é recomendável que esses servidores sejam reescritos para dar suporte a vários STAs (categoria 2).Servidor In-proc que suporta o modelo de apartamento de thread único (vários STAs) - marcado com
ThreadingModel=Apartment
:Um objeto fornecido por esse servidor espera ser acessado pelo mesmo STA do cliente pelo qual foi criado. Portanto, é semelhante a um objeto fornecido por um servidor in-proc de thread único. No entanto, os objetos fornecidos por esse servidor podem ser criados em várias STAs do processo, portanto, o servidor deve projetar seus pontos de entrada, como
DllGetClassObject
eDllCanUnloadNow
, e dados globais para uso multi-threaded. Por exemplo, se dois STAs de um processo criarem duas instâncias do objeto in-proc simultaneamente,DllGetClassObject
poderá ser chamado simultaneamente por ambos os STAs. Da mesma forma,DllCanUnloadNow
deve ser escrito para que o servidor esteja protegido contra descarregamento enquanto o código ainda está em execução no servidor.Se o servidor fornecer apenas uma instância da fábrica de classes para criar todos os objetos, a implementação da fábrica de classes também deverá ser projetada para uso multi-threaded porque é acessada por vários STAs clientes. Se o servidor criar uma nova instância da fábrica de classes sempre que
DllGetClassObject
for chamado, a fábrica de classes não precisará ser thread-safe. No entanto, o implementador deve sincronizar o acesso a todas as variáveis globais.Os objetos COM criados pela fábrica de classes não precisam ser thread-safe. No entanto, o acesso de variáveis globais deve ser sincronizado pelo implementador. Depois de criado por um thread, o objeto é sempre acessado por meio desse thread e todas as chamadas para o objeto são sincronizadas pelo COM. Os apartamentos do cliente que são diferentes do STA no qual o objeto foi criado devem acessar o objeto por meio de proxies. Esses proxies são criados quando o cliente realiza marshaling da interface entre seus apartamentos.
Qualquer cliente que cria um objeto STA por meio de sua fábrica de classes obtém um ponteiro direto para o objeto. Isso é diferente dos objetos in-proc de thread único, em que apenas o STA principal do cliente obtém um ponteiro direto para o objeto e todos os outros STAs que criam o objeto obtêm acesso ao objeto por meio de um proxy. Como o marshaling entre threads é lento em relação à chamada direta, a velocidade pode ser melhorada alterando um servidor in-proc de thread único para dar suporte a vários STAs.
Servidor In-proc que suporta apenas MTA - marcado com
ThreadingModel=Free
:Um objeto fornecido por esse servidor é seguro apenas para o MTA. Ele implementa sua própria sincronização e é acessado por vários threads de cliente ao mesmo tempo. Esse servidor pode ter um comportamento incompatível com o modelo STA. (Por exemplo, pelo uso da fila de mensagens do Windows de uma forma que interrompe a bomba de mensagens de um STA.) Além disso, marcando o modelo de threading do objeto como "Livre", o implementador do objeto está declarando o seguinte: esse objeto pode ser chamado de qualquer thread do cliente, mas esse objeto também pode passar ponteiros de interface diretamente (sem marshaling) para qualquer thread que ele criou e esses threads podem fazer chamadas por meio desses ponteiros. Portanto, se o cliente passar um ponteiro de interface para um objeto implementado pelo cliente (como um coletor) para esse objeto, ele poderá optar por chamar de volta por meio desse ponteiro de interface de qualquer thread criado. Se o cliente for um STA, uma chamada direta de um thread, que é diferente do thread que criou o objeto coletor, estará com erro (conforme demonstrado em 2 acima). Portanto, o COM sempre garante que os clientes em threads associados a um STA obtenham acesso a esse tipo de objeto in-proc somente por meio de um proxy. Além disso, esses objetos não devem ser agregados ao marshaler de thread livre porque isso permite que eles sejam executados diretamente em threads STA.
Servidor In-proc que suporta modelo de apartamento e free-threading - marcado com
ThreadingModel=Both
:Um objeto fornecido por esse servidor implementa sua própria sincronização e é acessado simultaneamente por vários apartamentos de cliente. Além disso, esse objeto é criado e usado diretamente, em vez de por meio de um proxy, em STAs ou no MTA de um processo de cliente. Como esse objeto é usado diretamente em STAs, o servidor deve empacotar interfaces de objetos, possivelmente de outros servidores, entre threads para que seu acesso a qualquer objeto de maneira apropriada ao threading seja garantido. Além disso, marcando o modelo de threading do objeto como "Ambos", o implementador do objeto está declarando o seguinte: esse objeto pode ser chamado de qualquer thread do cliente, mas todos os retornos de chamada desse objeto para o cliente serão feitos apenas no apartamento no qual o objeto recebeu o ponteiro da interface para o objeto de retorno de chamada. O COM permite que esse objeto seja criado diretamente em um STA, bem como em um MTA do processo do cliente.
Como qualquer apartamento que cria esse objeto sempre obtém um ponteiro direto em vez de um ponteiro proxy,
ThreadingModel "Both"
os objetos fornecem melhorias de desempenho em relação aosThreadingModel "Free"
objetos quando carregados em um STA.Como um
ThreadingModel "Both"
objeto também foi projetado para acesso ao MTA (ele é thread-safe internamente), ele pode acelerar o desempenho agregando-se com o marshaler fornecido peloCoCreateFreeThreadedMarshaler
. Esse objeto fornecido pelo sistema é agregado a qualquer objeto de chamada e marshals personalizados direcionam ponteiros para o objeto em todos os apartamentos no processo. Os clientes em qualquer apartamento, seja um STA ou MTA, podem acessar o objeto diretamente em vez de por meio de um proxy. Por exemplo, um cliente de modelo STA cria o objeto in-proc em STA1 e empacota o objeto para STA2. Se o objeto não for agregado ao marshaler de thread livre, o STA2 obterá acesso ao objeto por meio de um proxy. Em caso afirmativo, o marshaler de thread livre fornece ao STA2 um ponteiro direto para o objetoObservação
Deve-se ter cuidado ao agregar com o marshaler de thread livre. Por exemplo, suponha que um objeto marcado como
ThreadingModel "Both"
(e também agregando com o marshaler de thread livre) tenha um membro de dados que é um ponteiro de interface para outro objeto cujoThreadingModel
é "Apartment". Em seguida, suponha que um STA crie o primeiro objeto e, durante a criação, o primeiro objeto crie o segundo objeto. De acordo com as regras discutidas acima, o primeiro objeto agora está segurando um ponteiro direto para o segundo objeto. Agora, suponha que o STA empacote o ponteiro de interface para o primeiro objeto para outro apartamento. Como o primeiro objeto é agregado ao marshaler de thread livre, um ponteiro direto para o primeiro objeto é fornecido ao segundo apartamento. Se o segundo apartamento chamar por meio desse ponteiro e se essa chamada fizer com que o primeiro objeto chame por meio do ponteiro de interface para o segundo objeto, ocorrerá um erro, pois o segundo objeto não deve ser chamado diretamente do segundo apartamento. Se o primeiro objeto estiver segurando um ponteiro para um proxy para o segundo objeto em vez de um ponteiro direto, isso causará um erro diferente. Os proxies do sistema também são objetos COM associados a um e apenas um apartamento. Eles mantêm o controle de seu apartamento para evitar certas circularidades. Portanto, um objeto que chama um proxy associado a um apartamento diferente do thread no qual o objeto está sendo executado receberá o retorno RPC_E_WRONG_THREAD do proxy e a chamada falhará.
Interoperabilidade do modelo de threading entre clientes e objetos em processo
Todas as combinações de interoperabilidade do modelo de threading são permitidas entre clientes e objetos em processo.
O COM permite que todos os clientes do modelo STA interoperem com objetos in-proc de thread único criando e acessando o objeto no STA principal do cliente e empacotando-o para o STA do cliente que chamou CoCreateInstance[Ex]
.
Se um MTA em um cliente criar um servidor in-proc de modelo STA, o COM ativará um STA "host" no cliente. Esse STA de host cria o objeto e o ponteiro da interface é empacotado de volta para o MTA. Da mesma forma, quando um STA cria um servidor MTA in-proc, o COM cria um MTA de host no qual o objeto é criado e empacotado de volta para o STA. A interoperabilidade entre o modelo de encadeamento único e o modelo MTA é tratada de forma semelhante porque o modelo de encadeamento único é apenas um caso degenerado do modelo STA.
O código de marshaling deve ser fornecido para qualquer interface personalizada que um servidor in-proc implemente se quiser dar suporte à interoperabilidade que exija que o COM realize marshaling da interface entre os apartamentos do cliente. Consulte a seção REFERÊNCIAS abaixo para obter mais informações.
Relação entre o modelo de threading e o objeto de fábrica de classe retornado
Uma definição precisa de servidores in-proc sendo "carregados" em apartamentos é explicada nas duas etapas a seguir:
Se a DLL que contém a classe de servidor in-proc não tiver sido carregada anteriormente (mapeada no espaço de endereço do processo) pelo carregador do sistema operacional, essa operação será executada e o
DLLGetClassObject
COM obterá o endereço da função exportada pela DLL. Se a DLL tiver sido carregada anteriormente por um thread associado a qualquer apartamento, esse estágio será ignorado.O COM usa o thread (ou, no caso do MTA, um dos threads) associado ao apartamento de "carregamento" para chamar a função exportada
DllGetClassObject
pela DLL solicitando o CLSID da classe necessária. O objeto de fábrica retornado é então usado para criar instâncias de objetos da classe.A segunda etapa (a chamada de
DllGetClassObject
por COM) ocorre toda vez que um cliente ligaCoGetClassObject
/CoCreateIntance[Ex]
, mesmo de dentro do mesmo apartamento. Em outras palavras,DllGetClassObject
pode ser chamado muitas vezes por um thread associado ao mesmo apartamento; tudo depende de quantos clientes nesse apartamento estão tentando obter acesso a um objeto de fábrica de classe para essa classe.
Os autores de implementações de classe e, em última análise, o autor do pacote DLL de um determinado conjunto de classes têm total liberdade para decidir qual objeto de fábrica retornar em resposta à chamada de DllGetClassObject
função. O autor do pacote DLL tem a palavra final porque o código "por trás" do único ponto de entrada em toda DllGetClassObject()
a DLL é o que tem o primeiro e potencialmente último direito de decidir o que fazer. As três possibilidades típicas são:
DllGetClassObject
retorna um objeto de fábrica de classe exclusivo por thread de chamada (o que significa um objeto de fábrica de classe por STA e potencialmente várias fábricas de classe dentro do MTA).DllGetClassObject
Sempre retorna o mesmo objeto de fábrica de classe, independentemente da identidade do thread de chamada ou do tipo de apartamento associado ao thread de chamada.DllGetClassObject
retorna um objeto de fábrica de classe exclusivo por apartamento de chamada (um por apartamento em STA e MTA).
Há outras possibilidades para a relação entre chamadas para DllGetClassObject
e o objeto de fábrica de classe retornado (como um novo objeto de fábrica de classe por chamada para DllGetClassObject
), mas elas não parecem ser úteis no momento.
Resumo dos modelos de threading de cliente e objeto para servidores In-Proc
A tabela a seguir resume a interação entre diferentes modelos de threading quando um thread de cliente chama CoGetClassObject
pela primeira vez uma classe implementada como um servidor in-proc.
Tipos de clientes/threads:
- o cliente está sendo executado em um thread associado ao STA "principal" (primeiro thread a ser chamado
CoInitialize
ouCoInitializeEx
comCOINIT_APARTMENTTHREADED
sinalizador) - chame este STA0 (também chamado de modelo de threading único). - cliente está em execução em um thread associado a qualquer outro STA [ASCII 150] chame este STA*.
- cliente está em execução em um thread associado ao MTA.
Tipos de servidores DLL:
- O servidor não tem chave
ThreadingModel
- chame isso de "Nenhum". - O servidor está marcado como "Apartamento" - chame isso de "Apt".
- O servidor está marcado como "Livre".
- O servidor está marcado como "Ambos".
Ao ler a tabela abaixo, tenha em mente a definição feita acima de "carregar" um servidor em um apartamento.
Client Server Result
STA0 None Direct access; server loaded into STA0
STA* None Proxy access; server loaded into STA0.
MTA None Proxy access; server loaded into STA0; STA0 created automatically by COM if necessary;
STA0 Apt Direct access; server loaded into STA0
STA* Apt Direct access; server loaded into STA*
MTA Apt Proxy access; server loaded into an STA created automatically by COM.
STA0 Free Proxy access; server is loaded into MTA MTA created automatically by COM if necessary.
STA* Free Same as STA0->Free
MTA Free Direct access
STA0 Both Direct access; server loaded into STA0
STA* Both Direct access; server loaded into STA*
MTA Both Direct access; server loaded into the MTA
Referências
SDK na CoRegisterMessageFilter()
interface e IMessageFilter
.