【问题标题】:When do I need to call CoInitialize() in this scenario?在这种情况下,我什么时候需要调用 CoInitialize()?
【发布时间】:2012-02-15 01:11:01
【问题描述】:

我正在 Delphi XE2 中构建一个多线程 Windows 服务应用程序,它使用 ADO 数据库组件连接到 SQL Server。在线程内部之前我已经多次使用CoInitialize(nil);,但在这种情况下,我有一个我不确定的函数。

此函数称为TryConnect,它尝试使用给定的连接字符串连接到数据库。它在连接成功时返回 true 或 false。问题是这个函数会在主服务线程内部和外部使用,它会创建自己的临时TADOConnection组件,这需要CoInitialize...

我的问题是我还需要在这个函数中调用CoInitialize 吗?如果我这样做,并且由于服务的执行过程也使用CoInitialize,如果我从服务中调用此函数,它们会干扰吗? TryConnect 函数位于从主服务线程创建的对象内部(但最终将移至其自己的线程)。我需要知道从同一个线程(和CoUninitialize)调用CoInitialize() 两次是否会干扰 - 以及如何正确处理这种情况。

下面是代码...

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

【问题讨论】:

  • 你阅读STAs上的文章了吗?使用CoInitialize() 意味着您拥有 STA。必须为每个线程初始化 COM,并且必须平衡 CoInitialize()/CoUnitialize() 调用。我不知道这在 Delphi 中是如何工作的,但您可能还必须在那里的线程之间编组指针。

标签: multithreading delphi activex ado delphi-xe2


【解决方案1】:

CoInitialize 必须在每个使用 COM 的线程中调用,无论它是什么线程,或者它是否有父线程或子线程。如果线程使用COM,它必须调用CoInitialize

这里的正确答案是“视情况而定”。由于您知道服务线程调用了CoInitialize,因此如果从服务线程调用TryConnect,则不需要再次调用它。如果可以调用它的其他线程也调用了CoInitialize,则不需要调用它,因为该函数将在调用线程下运行。

MSDN 文档专门解决了这个问题(已添加重点):

通常,COM 库仅在线程上初始化一次。 在同一线程上对 CoInitialize 或 CoInitializeEx 的后续调用将成功,只要它们不尝试更改并发模型,但将返回 S_FALSE。为了优雅地关闭 COM 库,对 CoInitialize 或 CoInitializeEx 的每次成功调用,包括那些返回 S_FALSE 的调用,都必须通过对 CoUninitialize 的相应调用来平衡。但是,应用程序中调用 CoInitialize 的第一个线程使用 0(或带有 COINIT_APARTMENTTHREADED 的 CoInitializeEx 必须是调用 CoUninitialize 的最后一个线程。否则,STA 上对 CoInitialize 的后续调用将失败,应用程序将无法运行。

所以答案是:如果您不确定,请致电CoInitialize。在try..finally 块中执行此操作,并在finally 中调用CoUnitialize,或者在构造函数中初始化并在析构函数中取消初始化。

【讨论】:

  • 所以CoInitialize可以被多次调用,只要它被包裹在try块中并且最后应该在每个之后CoUninitialize
  • 是的,如我回答的最后一段所述。
【解决方案2】:

您的服务线程根本不应该做任何工作。它应该专门用于响应 Service Manager 调用。服务的 OnExecute 或 OnStart/OnStop 应控制代表服务功能的“MainWorkThread”的实例化和执行。示例见https://stackoverflow.com/a/5748495/11225

主工作线程可以做真正的工作和/或将它委托给其他线程。每个可以使用 COM 的线程都应该有 CoInitialize/CoUninitialize 调用,实现这一点的最简单方法是将它们编码在线程(被覆盖的)Execute 方法的最外层 try finally 块中。

TDBPool 或任何其他使用 COM 的类不应关注 CoInitialize 和 CoUninitialize 调用。这些方法需要在每个可以使用 COM 的线程中调用,而一个类不知道也不应该知道它将在哪个线程中执行。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-01-25
    • 1970-01-01
    • 2010-11-18
    • 2016-08-08
    • 2019-10-29
    • 2016-07-17
    • 1970-01-01
    相关资源
    最近更新 更多