Вызов событий COM из разных потоков
От: silart  
Дата: 05.11.09 03:02
Оценка:
Добрый день!

Столкнулся со следующей проблемой. Имеется inproc ActiveX компонент. Он генерирует события в отдельном потоке, причем с довольно большой частотой.
Событийный поток занимается обслуживанием очереди событий. Я привожу код полностью процедуры потока.

void EventManager::Routine()
{
HRESULT hr = E_FAIL;
tstring name;
vector<ml::com::Variant> args;
com_ptr<IDispatch, IID_IDispatch> pIEvent;
com_ptr<IEnumConnections, IID_IEnumConnections> pEnumConnections;

    // Блокировка потока, если список стал пустым
    if ( m_EventList.empty() )
    {
        mutex::scoped_lock lock(monitor);
        m_NewEvent.wait(lock);
    }

    // Предотвращаем одновременную генерацию событий из разных потоков.
    // (При использовании нескольких экземпляров компоненты в одном процессе)
    ExternalLock ext_lock;

mutex::scoped_lock cs_InvokeEvent(m_InvokeEventMutex);

    if ( !ExtractEvent(name, args) )
        return;

    DispParams dp(args);

cs_InvokeEvent.unlock();
    hr = pIConnectionPoint->EnumConnections( &pEnumConnections );
cs_InvokeEvent.lock();

    if (hr == RPC_E_CALL_CANCELED)
        return;
    ML_ASSERT( SUCCEEDED(hr) );

    CONNECTDATA cd;

cs_InvokeEvent.unlock();
    hr = pEnumConnections->Next(1, &cd, NULL);
cs_InvokeEvent.lock();

    if (hr == RPC_E_CALL_CANCELED)
        return;
    ML_ASSERT( SUCCEEDED(hr) );

cs_InvokeEvent.unlock();
    hr = cd.pUnk->QueryInterface(IID_IDispatch, (void**)&pIEvent);
cs_InvokeEvent.lock();

    if (hr == RPC_E_CALL_CANCELED) 
        return;
    ML_ASSERT( SUCCEEDED(hr) );

    BStr memberName( TtoW::from(name).c_str() );
    DISPID dispid;

cs_InvokeEvent.unlock();
    hr = pITypeInfo->GetIDsOfNames( memberName, 1, &dispid);
cs_InvokeEvent.lock();

    if (hr == RPC_E_CALL_CANCELED) 
        return;
    ML_ASSERT( SUCCEEDED(hr) );

cs_InvokeEvent.unlock();

    hr = pIEvent->Invoke(dispid, IID_NULL, 
        GetUserDefaultLCID(), DISPATCH_METHOD, 
        dp.get(), NULL, NULL, NULL);

cs_InvokeEvent.lock();

    if (hr == RPC_E_CALL_CANCELED) 
        return;
    ML_ASSERT( SUCCEEDED(hr) );
}


При старте событийный поток вызывает CoInitialize(NULL).

bool EventManager::InitDone(bool fInitDone)
{
    if (fInitDone)
    {
        CoInitialize(NULL);
        CoEnableCallCancellation(NULL);
        m_ThreadID = GetCurrentThreadId();
    }
    else
    {
        CoUninitialize();
    }

return true;
}


Если клиент, работает только с одним экземпляром COM объекта, то все отлично.
Как только клиент использует несколько экземпляров, все падает в тот момент, когда COM объекты начинают генерирование событий. Причем такой эффект не наблюдался на 2-х компьютерах, на которых велась разработка, он проявился на компьютере заказчика уже на объекте, где не представляется возможным подключиться отладчиком.
Долгие размышления привели меня к мысли использовать синглетный мьютекс в процедуре потока:

    // Предотвращаем одновременную генерацию событий из разных потоков.
    // (При использовании нескольких экземпляров компоненты в одном процессе)
    ExternalLock ext_lock;


т. е. мьютекс запрещает одновременное обращение к интерфейсу IDispatch из разных потоков.
Таким образом все работает, но мне непонятно почему проблема не проявилась сразу на компах разработчиков.
И вообще, правильно ли такое решение? Может в COM имеется более изящный подход к подобным проблемам?
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.