Многопоточное приложение C++ с использованием Matlab Engine

Я открываю движок Matlab в потоке инициализации, делая:

bool MY_MATLAB_ENGINE_o::Open()
{
    // Handle the case where engine is already open
    if( MatlabEngine )
    {
        return true;
    }
    else if( !( MatlabEngine = engOpen( 0 ) ) )
    {
        return false;
    }

    IsEngineOpen.SetValue( true );
    return true;
}

Функция engOpen() открывает COM-канал для Matlab. Как только механизм открыт, поток переходит в режим ожидания события.

Затем, в другом потоке, я делаю это:

bool MY_MATLAB_ENGINE_o::ChangeDirectory( QString strPath )
{
    QString strPathToScript = "cd('" + strPath + "');";

    QByteArray ba = strPathToScript.toLatin1();
    const char* cPathToScript = ba.data(); 

    if( MatlabEngine )
    {
        engEvalString( MatlabEngine, cPathToScript );

        return true;
    }

    return false;
}

Я получаю исключение первого шанса CoInitialize has not been called на engEvalString( MatlabEngine, cPathToScript );, которое, кажется, говорит мне, что COM-сервер Matlab недоступен (но движок Matlab все еще работает).

Когда я помещаю все в один поток, все работает нормально, но это не тот дизайн, который я имел в виду.

Я обнаружил, что в документации по движку Matlab отсутствует информация о движке + COM. Любая идея, как инициализировать движок и вызывать функции в отдельных потоках?

Спасибо !

ИЗМЕНИТЬ после ответа Роба

Я добавил этот метод в свой класс (создан во втором потоке):

bool MY_MATLAB_FUNCTION_CALL_o::PostThreadCreationHook()
{
    HRESULT hr;
    hr = CoInitializeEx(0, COINIT_MULTITHREADED); 
    if (FAILED(hr)) 
    { 
        return false;
    }

    return true;
}

И теперь, когда я звоню engEvalString( MatlabEngine, cPathToScript );, я получаю The application called an interface that was marshalled for a different thread исключение первого шанса :) Мне так весело сегодня утром! :)

So, CoMarshalInterface() ?


person CTZStef    schedule 08.10.2013    source источник


Ответы (1)


CoInitialize необходимо вызывать из каждого потока, в котором используется COM-объект, а не только из основного потока.

Прошло десять лет с тех пор, как я в последний раз автоматизировал Matlab, так что извините за ржавчину в дальнейшем. То, что вы получили сообщение об ошибке CoInitialize, предполагает, что вызов engOpen упаковывает базовые вызовы COM. К сожалению, это подвергает вас неожиданному воздействию червей, которыми является COM. Я думаю, вы правы, и что engOpen включает вызов CoInitialize, который инициализирует библиотеку COM в текущем потоке. Для доступа к COM-объектам из потока CoInitialize всегда должен вызываться в этом потоке перед любыми вызовами COM (кроме одной разрешенной функции COM, я не помню какой).

Мой совет — теперь изолировать все ваши вызовы Matlab в один поток. Если вы сделаете это, вам не придется делать явный вызов CoInitialize, и вы избежите любых последующих проблем с многопоточными COM. Вы можете заставить свою программу работать сегодня, вызвав CoInitialize во втором потоке, но однажды вы столкнетесь с другой проблемой COM.

[Я потратил около десяти лет на COM, и он полон медвежьих капканов. Вы могли бы потратить несколько недель на чтение технологии, которую Microsoft пыталась скрыть/убить с помощью .Net, но сейчас лучше просто выбрать простой (однопоточный) путь и забыть об этом.]

Обновление Боюсь, ваше редактирование завело вас в трясину моделей многопоточности COM. COINIT_MULTITHREADED эффективно сообщает COM, что вы собираетесь позаботиться обо всех мелких нюансах многопоточности, что почти наверняка не то, что вы хотите делать. COM работает с несколькими (последний раз, когда я обращал внимание, что это было три) моделями потоков, и параметр, который вы передаете CoInitializeEx, объявляет, какую из этих моделей вы хотите использовать.

Извиняюсь перед всеми, если нижеследующее немного не так.

Если вы укажете COINIT_MULTITHREADED, вам нужно либо знать, что вызываемый вами COM-объект является потокобезопасным, либо выполнить соответствующую блокировку (и маршаллинг интерфейсов и данных между потоками) самостоятельно.

COINIT_APARTMENTTHREADED, который, вероятно, использует engOpen, поскольку, по моему опыту, он наиболее распространен, позволяет библиотеке COM справляться с многопоточностью за вас. Библиотека может, например, создавать прокси-объекты и объекты-заглушки для передачи вызовов между потоками (или границами процессов, что произойдет, когда вы вызовете Matlab).

engOpen создал прокси-объект Matlab в вашем основном потоке. Этот прокси-объект можно вызвать из потока, в котором он был создан, и, если я правильно помню, из любого другого потока в «квартире» (где CoInitializeEx был вызван с COINIT_APARTMENTTHREADED.) Вы попытались вызвать через прокси из потока в другой модели многопоточности библиотека COM заметила и выдала упомянутую вами ошибку.

Во многих отношениях COM прекрасен, но его тонкости вызывают головную боль. Будьте благодарны, что вам никогда не придется использовать Distributed COM, что действительно неприятно!

Обновление 2 Мои старые воспоминания о моделях многопоточности COM ошибочны. На на этой странице MSDN указано, что на квартиру приходится один поток с COINIT_APARTMENTTHREADED. Доступ к COM-объектам можно получить с помощью одного и того же указателя интерфейса из всех потоков в подразделении, где они были созданы. Для COINIT_APARTMENTTHREADED это означает только поток, в котором был создан объект. В COINIT_MULTITHREADED это будут все потоки в многопоточном апартаменте, но (1) вы не можете выбрать, на каком потоке создается механизм Matlab, если вы используете engOpen и (2) пытаетесь вызвать COM-объект, который вы не писать из многопоточного апартамента рискованно. Первоначальная модель многопоточности OLE допускала вызовы COM только из основного потока GUI, кстати.

person RobH    schedule 08.10.2013
comment
Дело в том, что я нигде не вызываю CoInitialize. Угадайте, что это должно быть выполнено engOpen? (это функция движка Matlab). Более того, я никогда не использовал CoInitialize, какие-либо предложения, как я мог это сделать? - person CTZStef; 08.10.2013
comment
РобХ, пожалуйста, взгляните на мою правку выше. Думаю, я в конечном итоге перенесу все в ту же тему, как вы предложили :D - person CTZStef; 08.10.2013
comment
@CTZStef Я добавил еще немного. - person RobH; 10.10.2013
comment
Я пробовал COINIT_APARTMENTTHREADED, но все равно получил приложение, вызывающее интерфейс, который был упорядочен для другого потока. исключение первого шанса. - person CTZStef; 10.10.2013
comment
Я добавил еще одно обновление. Извините, я неправильно указал детали COINIT_APARTMENTTHREADED. Суть в том, что вам нужно будет выполнять все вызовы Matlab из одного потока, если вы используете вызовы библиотеки engOpen и т.д. Альтернативой является выполнение всех вызовов Matlab напрямую с использованием COM-интерфейсов, но я бы этого не рекомендовал. - person RobH; 14.10.2013
comment
Спасибо, РобХ. Я связался со службой поддержки Matlab, и до сих пор я получил ответ, что движок Matlab не является потокобезопасным... да ладно. - person CTZStef; 15.10.2013
comment
Когда вы вызываете Matlab с помощью COM, он запускается как отдельный процесс, поэтому COM-объект, с которым вы взаимодействуете, является прокси-сервером и намного проще, чем сам массивный движок Matlab. Жаль, что они не сделали его потокобезопасным. Конечно, ничто не мешает вам создать движок и вызвать его из рабочего потока, хотя вам придется самостоятельно создать механизм связи между этим потоком и остальной частью вашего приложения. - person RobH; 15.10.2013
comment
Спасибо, РобХ, хотя я не буду продолжать это из соображений времени, это было очень поучительно. Большое тебе спасибо ! - person CTZStef; 16.10.2013