Single-threaded (appartement à un seul thread)

Avertissement

Risque de blocage avec des appels asynchrones/await et bloquants sur des threads STA : Dans le code managé (.NET), la boucle de message STA est essentielle pour la répartition des appels COM. Le blocage d'un thread STA avec Task.Wait(), Task.Result, Thread.Sleep() ou ManualResetEvent.WaitOne() empêche l'exécution des rappels COM et des appels entre cloisonnements, ce qui provoque un interblocage.

// ❌ 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 vous ne pouvez pas utiliser await, utilisez CoWaitForMultipleHandles (natif) ou un frame de répartiteur/une pompe de messages (managé) au lieu de primitives d'attente brutes sur les threads STA.

L'utilisation de cloisonnements à thread unique (processus de modèle de cloisonnement) offre un paradigme basé sur les messages pour gérer plusieurs objets s'exécutant simultanément. Il vous permet d’écrire du code plus efficace en autorisant un thread, pendant qu’il attend la fin d’une opération fastidieuse, pour permettre à un autre thread d’être exécuté.

Chaque thread d'un processus initialisé en tant que processus de modèle de cloisonnement, et qui récupère et distribue les messages de fenêtre, est un thread de cloisonnement à thread unique. Chaque thread réside dans son propre cloisonnement. Au sein d'un cloisonnement, les pointeurs d'interface peuvent être transmis sans marshaling et, par conséquent, tous les objets d'un même thread de cloisonnement à thread unique communiquent directement.

Un regroupement logique d'objets associés qui s'exécutent tous sur le même thread et doivent donc s'exécuter de manière synchrone peut résider sur le même thread de cloisonnement à thread unique. Toutefois, un objet de modèle d’appartement ne peut pas résider sur plusieurs threads. Les appels à des objets appartenant à d’autres threads doivent être effectués dans le contexte du thread propriétaire ; COM distribué bascule donc automatiquement vers le thread approprié lorsque vous passez par un proxy.

Les modèles interprocess et interthread sont similaires. Quand il est nécessaire de passer un pointeur d’interface à un objet d’un autre appartement (sur un autre thread) au sein du même processus, vous utilisez le même modèle de marshaling que les objets dans différents processus utilisent pour passer des pointeurs entre les limites du processus. En obtenant un pointeur vers l'objet de marshaling standard, vous pouvez effectuer le marshaling des pointeurs d'interface au-delà des limites des threads (entre cloisonnements) de la même manière qu'entre des processus. (Le marshaling des pointeurs d'interface est obligatoire lorsqu'ils sont transmis entre des cloisonnements.)

Les règles relatives aux cloisonnements à thread unique sont simples, mais il est important de les respecter scrupuleusement :

  • Chaque objet ne doit résider que sur un seul thread (au sein d'un cloisonnement à thread unique).
  • Initialisez la bibliothèque COM pour chaque thread.
  • Effectuez le marshaling de tous les pointeurs vers des objets lorsque vous les transmettez entre des cloisonnements.
  • Chaque cloisonnement à thread unique doit disposer d'une boucle de messages pour gérer les appels provenant d'autres processus et cloisonnements au sein du même processus. Les cloisonnements à thread unique sans objets (client uniquement) ont également besoin d'une boucle de messages pour distribuer les messages de diffusion utilisés par certaines applications.
  • Les objets basés sur une DLL ou in-process n'appellent pas les fonctions d'initialisation COM ; ils enregistrent à la place leur modèle de threading à l'aide de la valeur nommée ThreadingModel sous la clé InprocServer32 du Registre. Les objets prenant en charge les cloisonnements doivent également implémenter avec soin les points d'entrée de la DLL. Des considérations particulières s'appliquent au threading des serveurs in-process. Pour plus d’informations, consultez Problèmes de multithreading du serveur In-Process.

Bien que plusieurs objets puissent vivre sur un seul thread, aucun objet de modèle d’appartement ne peut vivre sur plusieurs threads.

Chaque thread d’un processus client ou d’un serveur hors processus doit appeler CoInitialize, ou appeler CoInitializeEx et spécifier COINIT_APARTMENTTHREADED pour le paramètre dwCoInit. Le cloisonnement principal correspond au thread qui appelle CoInitializeEx en premier. Pour plus d’informations sur les serveurs in-process, consultez Problèmes de multithreading des serveurs in-process.

Tous les appels à un objet doivent être effectués sur son thread (au sein de son cloisonnement). Il est interdit d’appeler un objet directement à partir d’un autre thread ; l’utilisation d’objets de cette manière libre-thread peut entraîner des problèmes pour les applications. Cette règle implique que le marshaling de tous les pointeurs vers des objets est obligatoire lorsqu'ils sont transmis entre des cloisonnements. COM fournit les deux fonctions suivantes à cet effet :

Ces fonctions encapsulent les appels aux fonctions CoMarshalInterface et CoUnmarshalInterface, qui nécessitent l’utilisation de l’indicateur MSHCTX_INPROC.

En général, le marshaling est effectué automatiquement par COM. Par exemple, lorsque vous transmettez un pointeur d'interface en tant que paramètre dans un appel de méthode sur un proxy vers un objet situé dans un autre cloisonnement, ou lorsque vous appelez CoCreateInstance, COM effectue automatiquement le marshaling. Toutefois, dans certains cas particuliers où l'auteur de l'application transmet des pointeurs d'interface entre des cloisonnements sans utiliser les mécanismes COM habituels, il doit gérer manuellement le marshaling.

Si un appartement (Appartement 1) dans un processus a un pointeur d’interface et un autre appartement (Apartment 2) nécessite son utilisation, Apartment 1 doit appeler CoMarshalInterThreadInterfaceInStream pour marshaler l’interface. Le flux créé par cette fonction est thread-safe et doit être stocké dans une variable accessible par Apartment 2. Le cloisonnement 2 doit transmettre ce flux à CoGetInterfaceAndReleaseStream pour annuler le marshaling de l'interface et récupère un pointeur vers un proxy par l'intermédiaire duquel il peut accéder à l'interface. L’appartement principal doit rester actif jusqu’à ce que le client ait terminé toutes les opérations COM (car certains objets in-process sont chargés dans l’appartement principal, comme décrit dans Problèmes de threading des serveurs in-process). Une fois qu’un objet a été passé entre les threads de cette façon, il est très facile de passer des pointeurs d’interface en tant que paramètres. Ainsi, le COM distribué effectue le marshaling et le changement de thread pour l’application.

Pour gérer les appels provenant d'autres processus et cloisonnements au sein du même processus, chaque cloisonnement à thread unique doit disposer d'une boucle de messages. Cela signifie que la fonction de travail du thread doit avoir une boucle GetMessage/DispatchMessage. Si d’autres primitives de synchronisation sont utilisées pour communiquer entre les threads, la fonction MsgWaitForMultipleObjects peut être utilisée pour attendre les messages et les événements de synchronisation de threads. La documentation de cette fonction présente un exemple de ce type de boucle de combinaison.

COM crée une fenêtre masquée à l'aide de la classe Windows "OleMainThreadWndClass" dans chaque cloisonnement à thread unique. Un appel à un objet est reçu en tant que message de fenêtre pour cette fenêtre masquée. Lorsque le cloisonnement de l'objet récupère et distribue le message, la fenêtre masquée le reçoit. La procédure de fenêtre appelle ensuite la méthode d’interface correspondante de l’objet.

Lorsque plusieurs clients appellent un objet, les appels sont placés dans la file d'attente des messages et l'objet reçoit un appel chaque fois que son cloisonnement récupère et distribue les messages. Étant donné que les appels sont synchronisés par COM et que les appels sont toujours remis par le thread qui appartient à l’appartement de l’objet, les implémentations d’interface de l’objet n’ont pas besoin de fournir de synchronisation. Les cloisonnements à thread unique peuvent implémenter IMessageFilter afin de pouvoir annuler des appels ou recevoir des messages de fenêtre lorsque cela est nécessaire.

L’objet peut faire l’objet d’une réentrance si l’une des implémentations de ses méthodes d’interface récupère et distribue des messages ou effectue un appel ORPC vers un autre thread, provoquant ainsi l’acheminement d’un autre appel à l’objet (par le même apartment). OLE n’empêche pas la réentrance sur le même thread, mais elle peut aider à assurer la sécurité des threads. C’est identique à la manière dont une procédure de fenêtre peut être réentrée si elle récupère et répartit des messages alors qu’elle traite un message. Toutefois, l'appel d'un serveur de cloisonnement à thread unique hors processus qui appelle un autre serveur de cloisonnement à thread unique permet au premier serveur d'être réentré.

Accès aux interfaces dans différents appartements

choix du modèle de threading

Cloisonnements multithread

Problèmes de thread de serveur in-process

Processus, Threads et cloisonnements

Communication en cloisonnement monothread et multithread