Apartamentos de un solo hilo

Warning

Riesgo de interbloqueo con async/await y llamadas bloqueantes en subprocesos STA: En el código administrado (.NET), el bucle de mensajes STA es fundamental para el envío de llamadas COM. Bloquear un subproceso STA con Task.Wait(), Task.Result, Thread.Sleep() o ManualResetEvent.WaitOne() impide que se completen las llamadas de retorno COM y las llamadas entre apartamentos, lo que provoca un interbloqueo.

// ❌ 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
}

Si no puedes utilizar await, utiliza CoWaitForMultipleHandles (nativo) o un marco de despacho/bomba de mensajes (administrado) en lugar de primitivas de espera sin procesar en subprocesos STA.

El uso de apartamentos de un solo subproceso (el proceso de modelo de apartamento) ofrece un paradigma basado en mensajes para administrar múltiples objetos que se ejecutan simultáneamente. Permite escribir código más eficaz al permitir que se ejecute un subproceso, mientras espera a que se complete una operación que consume mucho tiempo, para permitir que se ejecute otro subproceso.

Cada hilo de un proceso que se inicializa como un proceso de modelo de apartamento y que recupera y distribuye mensajes de ventana es un hilo de apartamento de un solo hilo. Cada hilo reside dentro de su propio apartamento. Dentro de un apartamento, los punteros de interfaz se pueden pasar sin necesidad de marshaling y, por lo tanto, todos los objetos de un hilo de apartamento de un solo hilo se comunican directamente.

Una agrupación lógica de objetos relacionados que se ejecutan todos en el mismo hilo y que, por lo tanto, deben tener una ejecución sincrónica, podría residir en el mismo hilo de apartamento de un solo hilo. Sin embargo, un objeto del modelo de apartamento no puede residir en más de un hilo. Las llamadas a objetos de otros hilos deben realizarse dentro del contexto del hilo propietario, por lo que COM distribuido cambia de hilo automáticamente al invocar un proxy.

Los modelos interproceso e interhilo son similares. Cuando es necesario pasar un puntero de interfaz a un objeto situado en otro apartamento (en otro hilo) dentro del mismo proceso, se utiliza el mismo modelo de marshalling que emplean los objetos de procesos diferentes para pasar punteros a través de los límites entre procesos. Al obtener un puntero al objeto de marshalling estándar, se pueden marshallar punteros de interfaz a través de los límites entre hilos (entre apartamentos) de la misma forma que se hace entre procesos. (Los punteros de interfaz deben marshalizarse cuando se pasan entre apartamentos.)

Las reglas para los apartamentos de un solo hilo son sencillas, pero es importante seguirlas con atención:

  • Cada objeto debe residir en un único hilo (dentro de un apartamento de un solo hilo).
  • Inicialice la biblioteca COM para cada subproceso.
  • Marshalice todos los punteros a objetos al pasarlos entre apartamentos.
  • Cada apartamento de un solo hilo debe tener un bucle de mensajes para administrar las llamadas procedentes de otros procesos y apartamentos dentro del mismo proceso. Los apartamentos de un solo hilo sin objetos (solo cliente) también necesitan un bucle de mensajes para distribuir los mensajes de difusión que utilizan algunas aplicaciones.
  • Los objetos basados en DLL o en proceso no llaman a las funciones de inicialización de COM; en su lugar, registran su modelo de subprocesos con el valor con nombre ThreadingModel bajo la clave InprocServer32 del Registro. Los objetos compatibles con apartamentos también deben escribir los puntos de entrada de las DLL con cuidado. Existen consideraciones especiales que se aplican a los subprocesos de los servidores en proceso. Para obtener más información, consulte Problemas de subprocesamiento en el servidor en proceso.

Aunque varios objetos pueden existir en un único subproceso, ningún objeto del modelo apartment puede existir en más de un subproceso.

Cada subproceso de un proceso de cliente o un servidor fuera de proceso debe llamar a CoInitializeo llamar a CoInitializeEx y especificar COINIT_APARTMENTTHREADED para el parámetro dwCoInit. El apartamento principal es el subproceso que llama primero a CoInitializeEx. Para obtener información sobre los servidores en proceso, consulte Problemas de subprocesos en servidores en proceso.

Todas las llamadas a un objeto deben realizarse en su subproceso (dentro de su apartamento). Está prohibido invocar un objeto directamente desde otro hilo; usar objetos de esta forma libre entre hilos podría causar problemas en las aplicaciones. Esta regla implica que todos los punteros a objetos deben ser marshalizados cuando se pasan entre apartamentos. COM proporciona las dos funciones siguientes para este propósito:

Estas funciones encapsulan las llamadas a las funciones CoMarshalInterface y CoUnmarshalInterface, que requieren el uso de la marca MSHCTX_INPROC.

Por lo general, la marshalización la realiza automáticamente COM. Por ejemplo, al pasar un puntero a una interfaz como parámetro en una llamada a un método de un proxy a un objeto de otro apartamento, o al llamar a CoCreateInstance, COM realiza el marshalling automáticamente. Sin embargo, en algunos casos especiales, en los que el desarrollador de la aplicación pasa punteros a interfaces entre apartamentos sin utilizar los mecanismos normales de COM, debe encargarse de la marshalling manualmente.

Si un apartamento (Apartamento 1) de un proceso tiene un puntero a una interfaz y otro apartamento (Apartamento 2) necesita utilizarlo, el Apartamento 1 debe llamar a CoMarshalInterThreadInterfaceInStream para realizar la marshalling de la interfaz. La secuencia creada por esta función es segura para subprocesos y debe almacenarse en una variable accesible por Apartment 2. El apartamento 2 debe pasar este flujo a CoGetInterfaceAndReleaseStream para desmarcar la interfaz y obtendrá a cambio un puntero a un proxy a través del cual podrá acceder a la interfaz. El apartamento principal debe permanecer activo hasta que el cliente haya completado todo el trabajo de COM (porque algunos objetos en proceso se cargan en el apartamento principal, como se describe en Problemas de subprocesamiento del servidor en proceso). Una vez que un objeto se ha pasado entre subprocesos de esta manera, resulta muy sencillo pasar punteros de interfaz como parámetros. De este modo, COM distribuido se encarga del marcaje y del cambio de subproceso por la aplicación.

Para administrar las llamadas procedentes de otros procesos y apartamentos dentro del mismo proceso, cada apartamento de un solo hilo debe disponer de un bucle de mensajes. Esto significa que la función de trabajo del subproceso debe tener un bucle GetMessage/DispatchMessage. Si se usan otros primitivos de sincronización para comunicarse entre subprocesos, se puede usar la función MsgWaitForMultipleObjects para esperar mensajes y para eventos de sincronización de subprocesos. La documentación de esta función tiene un ejemplo de este tipo de bucle de combinación.

COM crea una ventana oculta utilizando la clase de Windows OleMainThreadWndClass en cada apartamento de un solo subproceso. Una llamada a un objeto se recibe en forma de mensaje de ventana en esta ventana oculta. Cuando el apartamento del objeto recupere y despache el mensaje, la ventana oculta lo recibirá. A continuación, el procedimiento de ventana llamará al método de interfaz correspondiente del objeto .

Cuando varios clientes llaman a un objeto, las llamadas se colocan en la cola de mensajes y el objeto recibirá una llamada cada vez que su apartamento recupere y distribuya mensajes. Dado que COM sincroniza las llamadas y que las llamadas siempre las entrega el subproceso que pertenece al apartamento del objeto, las implementaciones de interfaz del objeto no necesitan encargarse de la sincronización. Los apartamentos de un solo hilo pueden implementar IMessageFilter para poder cancelar llamadas o recibir mensajes de ventana cuando sea necesario.

Se puede volver a entrar en el objeto si una de las implementaciones de los métodos de su interfaz recupera y distribuye mensajes o realiza una llamada ORPC a otro hilo, lo que provoca que se entregue otra llamada al objeto (por parte del mismo apartamento). OLE no impide la reentrada en el mismo hilo, pero puede ayudar a garantizar la seguridad de los hilos. Esto es idéntico a la forma en que se puede volver a escribir un procedimiento de ventana si recupera y envía mensajes mientras procesa un mensaje. Sin embargo, llamar a un servidor de apartamento de un solo hilo fuera de proceso que, a su vez, llame a otro servidor de apartamento de un solo hilo permitirá que se vuelva a entrar en el primer servidor.

Acceso a las interfaces en distintos apartamentos

elegir el modelo de subprocesos

Contenedores multiproceso

Problemas de subprocesamiento del servidor en proceso

Procesos, subprocesos y apartamentos

Comunicación de un solo hilo y multihilo