Когда мне нужно вызвать CoInitialize () в этом сценарии? - PullRequest
5 голосов
/ 15 февраля 2012

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

Эта функция называется 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;

Ответы [ 2 ]

14 голосов
/ 15 февраля 2012

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, либо инициализируйте в конструкторе и деинициализируйте в деструкторе.

4 голосов
/ 15 февраля 2012

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

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

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

...