2011-04-18 10 views
3

J'ai déjà beaucoup travaillé sur le multithreading, mais je suis relativement nouveau dans COM. De toute façon, voici mon problème:Synchronisation en toute sécurité d'un thread COM

Je crée un thread de travail, qui s'enregistre en tant que STA et crée un objet COM. Ensuite, le thread de travail et le thread principal essaient de communiquer entre eux. En utilisant CoMarshalInterThreadInterfaceInStream et CoGetInterfaceAndReleaseStream, je peux obtenir les threads pour appeler des méthodes sur les objets COM dans l'autre thread.

Voici ce que le thread de travail ressemble à:

void workerThread() 
{ 
    CoInitialize(NULL); 
    MyLib::IFooPtr foo = ...; // create my COM object 

    // Marshall it so the main thread can talk to it 
    HRESULT hr = CoMarshalInterThreadInterfaceInStream(foo.GetIID(), 
                foo.GetInterfacePtr(), 
                &m_stream); 
    if (FAILED(hr)) { 
    // handle failure 
    } 

    // begin message loop, to keep this STA alive 
    MSG msg; 
    BOOL bRet; 
    while((bRet = GetMessage(&msg, NULL, 0, 0)) != 0) 
    { 
    if (bRet == -1) break; 

    DispatchMessage(&msg); 
    } 
} 

Dans le thread principal:

// launch the thread 
m_worker = boost::thread (&workerThread); 

// get the interface proxy 
MyLib::IFooPtr foo; 
LPVOID vp (NULL); 
HRESULT hr = CoGetInterfaceAndReleaseStream(m_stream, foo.GetIID(), &vp); 
if (SUCCEEDED(hr)) foo.Attach(static_cast<MyLib::IFoo*>(vp)); 

Cela crée l'objet (qui prend un certain temps pour initialiser) et permet le fil principal Parlez-en, et tout est correctement synchronisé avec les trucs COM Apartment. Autant que je sache en lisant msdn, cela semble être la bonne façon de faire les choses. Maintenant, le thread principal peut utiliser son proxy pour appeler des méthodes sur mon objet COM, et le thread de travail recevra ces appels sur la file d'attente de messages, en les envoyant correctement.

Cependant, qu'en est-il de la synchronisation de ces threads?

Il est évident que dans ce cas, je veux que le fil conducteur d'attendre pour appeler CoGetInterfaceAndReleaseStream jusqu'à ce que le thread de travail a créé ce flux via CoMarshalInterThreadInterfaceInStream. Mais comment puis-je faire cela en toute sécurité?

De MSDN, je devrais utiliser quelque chose comme MsgWaitForMultipleObjects, donc je peux attendre my_condition OU new_message_arrived, et je peux faire quelque chose comme:

// verbatim from msdn 
while (TRUE) 
{ 
    // wait for the event and for messages 
    DWORD dwReturn = ::MsgWaitForMultipleObjects(1, 
        &m_hDoneLoading, FALSE, INFINITE, QS_ALLINPUT); 

    // this thread has been reawakened. Determine why 
    // and handle appropriately. 
    if (dwReturn == WAIT_OBJECT_0) 
    // our event happened. 
    break ; 
    else if (dwReturn == WAIT_OBJECT_0 + 1) 
    { 
    // handle windows messages to maintain 
    // client liveness 
    MSG msg ; 
    while(::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) 
     ::DispatchMessage(&msg) ; 
    } 
} 

Mais comment puis-je mélanger boost::thread.join() et boost::condition.wait() avec MsgWaitForMultipleObjects? Est-ce possible, ou dois-je faire autre chose pour éviter une condition de course?

Répondre

3

Votre thread principal a une file d'attente de messages (doit être, puisque est un hôte STA), pourquoi ne pas simplement poster un message, PostThreadMessage? Publiez un message utilisateur (WM_USER + X) et votre pompe de messages de thread principal normal peut gérer ce message utilisateur comme une notification que l'objet COM a marshaled l'interface dans le flux et le thread principal est sûr d'appeler CoGetInterfaceAndReleaseStream.

Je dois vous rappeler cependant qu'avec votre design actuel votre thread de travail ne fait rien d'autre que lancer une pompe de message supplémentaire. Tout appel à une méthode sur votre interface à partir du thread principal bloquera, attendra que le thread de travail récupère le message de sa file d'attente de messages, traitera l'appel, répondra, puis le thread principal reprendra. Toutes les opérations seront au moins aussi lent que d'avoir l'objet COM hébergé dans le thread principal, plus le surcoût du marshaling COM entre les deux STA. Fondamentalement, il n'y a aucune concurrence entre les deux threads, à cause de comment fonctionne COM STA. Es-tu sûr que c'est ce que tu veux?

Modifier

(en omettant un tas de détails comme nombre de threads, la manipulation de délai d'attente, l'affectation d'un flux/IID/CLSID pour chaque travailleur, etc etc)

dans le.h:

HANDLE m_startupDone; 
volatile int m_threadStartCount; 

thread de travail:

void workerThread() 
{ 
    CoInitialize(NULL); 
    MyLib::IFooPtr foo = ...; // create my COM object 

    // Marshall it so the main thread can talk to it 
    HRESULT hr = CoMarshalInterThreadInterfaceInStream(foo.GetIID(), 
                foo.GetInterfacePtr(), 
                &m_stream); 
    if (FAILED(hr)) { 
    // handle failure 
    // remember to decrement and signal *even on failure* 
    } 

    if (0 == InterlockedDecrement(&m_threadStartCount)) 
    { 
    SetEvent (m_startupDone); 
    } 

    // begin message loop, to keep this STA alive 
    MSG msg; 
    BOOL bRet; 
    while((bRet = GetMessage(&msg, NULL, 0, 0)) != 0) 
    { 
    if (bRet == -1) break; 

    DispatchMessage(&msg); 
    } 
} 

dans le thread principal:

m_startupDone = CreateEvent (NULL, FALSE, FALSE, NULL); 
m_threadStartCount = <number of workerthreads> 

// launch the thread(s) 
m_worker = boost::thread (&workerThread); 
m_worker2 = boost::thread (&workerThread); 
... 

// now wait for tall the threads to create the COM object(s) 
if (WAIT_OBJECT0 != WaitForSingleObject(m_startupDone, ...)) 
{ 
    // handle failure like timeout 
} 
// By now all COM objects are guaranteed created and marshaled, unmarshall them all in main 
// here must check if all threads actually succeeded (could be as simple as m_stream is not NULL) 

// get the interface proxy 
MyLib::IFooPtr foo; 
LPVOID vp (NULL); 
HRESULT hr = CoGetInterfaceAndReleaseStream(m_stream, foo.GetIID(), &vp); 
if (SUCCEEDED(hr)) foo.Attach(static_cast<MyLib::IFoo*>(vp)); 
+0

non, je ne suis pas sûr que ce soit ce que je veux. J'ai affaire à une application monothread existante, essayant d'améliorer le temps de démarrage en ajoutant des threads. Je me rends compte que je négocie le temps de démarrage pour la durée de fonctionnement générale. J'espère finir par arriver à MTA, mais utiliser STA comme une étape intermédiaire dans le développement. – Tim

+0

Si vous avez lancé N threads, chacun créant un objet STA, chaque thread, dès qu'il a créé et assemblé l'interface dans le flux, décrémente un compteur (InterlockedDecrement). Celui qui le décrémente à 0 est le dernier, et peut signaler un événement. Le thread principal reste inactif en attente d'un événement, et quand il est signalé, il peut continuer en toute sécurité et ne pas écouter tous les flux. Cela a un impact minimal sur la logique de l'application héritée tout en permettant de créer des objets N COM en parallèle. –

+0

Pouvez-vous donner un exemple de cela? Je ne comprends pas comment utiliser 'PostThreadMessage' et' MsgWaitForMultipleObjects'. – Tim

Questions connexes