2012-02-15 2 views
3

Я создаю многопоточное приложение для Windows-приложений в Delphi XE2, которое использует компоненты базы данных ADO для подключения к SQL Server. Я использовал CoInitialize(nil); много раз перед внутренними потоками, но в этом случае у меня есть функция, о которой я не знаю.Когда мне нужно вызвать CoInitialize() в этом сценарии?

Эта функция называется TryConnect, которая пытается подключиться к базе данных с заданной строкой соединения. Он возвращает true или false при успешном соединении. Проблема заключается в том, что эта функция будет использоваться как внутри, так и вне основного потока службы, и он будет создавать свой собственный временный TADOConnection компонент, который требует CoInitialize ...

Мой вопрос мне нужно позвонить CoInitialize внутри этого функция тоже? Если я это сделаю, и поскольку процедура выполнения службы также использует CoInitialize, будут ли они вмешиваться, если я вызову эту функцию из службы? Функция TryConnect находится внутри объекта, который создается из основного потока обслуживания (но в конечном итоге будет перемещен в собственный поток). Мне нужно знать, будет ли вызов CoInitialize() дважды из того же потока (и CoUninitialize) будет мешать - и как правильно обрабатывать этот сценарий.

Вот код ниже ...

//This is the service app's execute procedure 
procedure TJDRMSvr.ServiceExecute(Sender: TService); 
begin 
    try 
    CoInitialize(nil); 
    Startup; 
    try 
     while not Terminated do begin 
     DoSomeWork; 
     ServiceThread.ProcessRequests(False); 
     end; 
    finally 
     Cleanup; 
     CoUninitialize; 
    end; 
    except 
    on e: exception do begin 
     PostLog('EXCEPTION in Execute: '+e.Message); 
    end; 
    end; 
end; 

//TryConnect might be called from same service thread and another thread 
function TDBPool.TryConnect(const AConnStr: String): Bool; 
var 
    DB: TADOConnection; //Do I need CoInitialize in this function? 
begin 
    Result:= False; 
    DB:= TADOConnection.Create(nil); 
    try 
    DB.LoginPrompt:= False; 
    DB.ConnectionString:= AConnStr; 
    try 
     DB.Connected:= True; 
     Result:= True; 
    except 
     on e: exception do begin 
     end; 
    end; 
    DB.Connected:= False; 
    finally 
    DB.Free; 
    end; 
end; 

Так выяснить, что это действительно делает, я мог бы иметь повод этого:

CoInitialize(nil); 
try 
    CoInitialize(nil); 
    try 
    //Do some ADO work 
    finally 
    CoUninitialize; 
    end; 
finally 
    CoUninitialize; 
end; 
+3

Вы прочитали статью о [STAs] (http://msdn.microsoft.com/en-us/library/windows/desktop/ms680112 (v = vs.85) .aspx)? Использование 'CoInitialize()' означает, что у вас есть STA. COM должен быть инициализирован для каждого потока, а 'CoInitialize()'/'CoUnitialize()' вызовы должны быть сбалансированы. Я не знаю, как это работает в Delphi, но вам, вероятно, придется маршевать указатели между потоками. –

ответ

10

CoInitialize должен вызываться в каждом отдельном потоке который использует COM, независимо от того, какой поток он имеет или имеет родительский поток или дочерние потоки. Если поток использует COM, он должен позвонить CoInitialize.

Правильный ответ здесь «это зависит». Поскольку вы знаете, что поток службы вызвал CoInitialize, если из сервисного потока вызывается TryConnect, его не нужно будет вызывать снова. Если другие потоки, которые могли бы назвать это, также вызвали CoInitialize, его не нужно будет вызывать, поскольку функция будет работать под вызывающим потоком.

документация MSDN специально рассматривает этот вопрос (подчеркнуто):

Как правило, библиотека COM-инициализируется на нить только один раз. Последующие вызовы CoInitialize или CoInitializeEx в том же потоке будут успешными, если они не попытаются изменить модель параллелизма, но вернут S_FALSE. Чтобы закрыть библиотеку COM изящно, каждый успешный вызов CoInitialize или CoInitializeEx, включая те, которые возвращают S_FALSE, должен быть сбалансирован соответствующим вызовом CoUninitialize. Однако первый поток в приложении, который вызывает CoInitialize с 0 (или CoInitializeEx с COINIT_APARTMENTTHREADED), должен быть последним потоком для вызова CoUninitialize. В противном случае последующие вызовы CoInitialize в STA не удастся, и приложение не будет работать.

Так ответ: Если вы не уверены, звоните CoInitialize. Сделайте это в блоке try..finally и вызовите CoUnitialize в finally или инициализируйте в конструкторе и uninitialize в деструкторе.

+0

Таким образом, это означает, что 'CoInitialize' может вызываться более одного раза, пока он завернут в блок' try', и он должен, наконец, «CoUninitialize» после каждого. –

+0

Да, как описано в заключительном пункте моего ответа. –

4

Ваш сервисный поток действительно не должен вообще выполнять какую-либо работу. Он должен использоваться исключительно для ответа на вызовы Service Manager.OnExecute или OnStart/OnStop службы должны управлять созданием и выполнением «MainWorkThread», который представляет функциональность вашего сервиса. См. Пример https://stackoverflow.com/a/5748495/11225.

Основная рабочая нить может выполнять реальную работу и/или делегировать ее другим потокам. Каждый поток, который может использовать COM, должен иметь вызовы CoInitialize/CoUninitialize, и самый простой способ добиться этого - закодировать их во внешнем большинстве, попробуйте, наконец, блок метода (переопределенного) потока.

TDBPool или любой другой класс, использующий COM, не должен касаться вызовов CoInitialize и CoUninitialize. Эти методы нужно вызывать в каждом потоке, который мог бы использовать COM, а класс не должен и не должен знать, в каком потоке он будет выполняться.

Смежные вопросы