【问题标题】:Safely synchronizing a COM thread安全同步 COM 线程
【发布时间】:2011-04-18 21:37:39
【问题描述】:

过去我在多线程方面做了很多工作,但我对 COM 还很陌生。无论如何,这是我的问题:

我创建了一个工作线程,它注册为一个 STA,并创建了一个 COM 对象。然后工作线程和主线程尝试相互通信。使用CoMarshalInterThreadInterfaceInStreamCoGetInterfaceAndReleaseStream,我可以让线程调用另一个线程中COM 对象的方法。

工作线程如下所示:

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); 
  }
}

在主线程中:

// 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));

这会创建对象(这需要一段时间来初始化),并允许主线程与之对话,并且一切都与 COM Apartment 的东西正确同步。据我从阅读 msdn 可以看出,这似乎是做事的正确方法。现在主线程可以使用它的代理来调用我的 COM 对象上的方法,并且工作线程将通过消息队列接收这些调用,并正确地分派它们。

但是,如何同步这些线程?

显然在这种情况下,我希望主线程等待调用CoGetInterfaceAndReleaseStream,直到工作线程通过CoMarshalInterThreadInterfaceInStream 创建该流之后。但我怎样才能安全地做到这一点呢?

MSDN 开始,我应该使用MsgWaitForMultipleObjects 之类的东西,所以我可以等待 my_condition 或 new_message_arrived,然后我可以执行以下操作:

// 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) ;
   }
}

但是如何将boost::thread.join()boost::condition.wait()MsgWaitForMultipleObjects 混合使用?这甚至可能吗,还是我必须做其他事情来避免竞争条件?

【问题讨论】:

    标签: c++ multithreading com boost synchronization


    【解决方案1】:

    您的主线程有一个消息队列(必须是,因为是 STA 主机),为什么不简单地向它发布消息,PostThreadMessage?发布用户消息 (WM_USER +X),您的普通主线程消息泵可以处理此用户消息,作为 COM 对象已将接口编组到流中的通知,并且主线程可以安全地调用 CoGetInterfaceAndReleaseStream

    我必须指出,在您当前的设计中,您的工作线程基本上只是运行一个额外的消息泵。从主线程对接口上的任何方法的任何调用都将阻塞,等待工作线程从其消息队列中获取消息,处理调用,响应,然后主线程将恢复。所有操作都将至少与将 COM 对象托管在主线程中一样慢,再加上两个 STA 之间来回编组的 COM 开销。由于 COM STA 的工作方式,两个线程之间基本上没有任何并发​​性。你确定这是你想要的吗?

    编辑

    (省略了一些细节,如线程数、超时处理、为每个工作人员分配流/IID/CLSID 等)

    在.h中:

    HANDLE m_startupDone;
    volatile int m_threadStartCount;
    

    工作线程:

    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); 
      }
    }
    

    在主线程中:

    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));
    

    【讨论】:

    • 不,我不确定这是我想要的。我正在处理一个遗留的单线程应用程序,试图通过添加线程来改善启动时间。我意识到我正在用启动时间换取一般运行时间。我希望最终能把它交给 MTA,但使用 STA 作为开发的中间步骤。
    • 如果您已经启动了 N 个线程,每个线程创建一个 STA 对象,那么让每个线程在创建接口并将其编组到流中后立即递减一个计数器 (InterlockedDecrement)。将其减为 0 的那个是最后一个,并且可以发出事件信号。主线程空闲等待这个事件,当收到信号时,它可以安全地继续并解组所有流。这对遗留应用程序逻辑的影响最小,同时允许并行创建 N 个 COM 对象。
    • 你能举个例子吗?我不清楚如何使用PostThreadMessageMsgWaitForMultipleObjects
    猜你喜欢
    • 1970-01-01
    • 2012-11-12
    • 2011-09-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-08-15
    相关资源
    最近更新 更多