Delphi AsyncCalls在终止线程时死锁(com+ dll终结)



我有一个大的Delphi 2007项目,我使用AsyncCall

我已经在一个控制台应用程序中提取并测试了多线程代码,一切都很好。

我的Delphi 2007项目产生了一个COM DLL,我有一些用c# - mstest编写的单元测试,调用DLL。

单元测试经常随机挂起(测试通过),我发现它发生在释放ThreadPool的asyncall单元结束部分。

destructor TThreadPool.Destroy;
var
  I: Integer;
  Call: TInternalAsyncCall;
begin
  FMaxThreads := FThreadCount; // Do not allocation new threads
  FDestroying := True; // => Sync in this thread because there is no other thread (required for FAsnycCallHead.Free)
  // Allow the threads to terminate if there is no task
  for I := FThreadCount - 1 downto 0 do
    FThreads[I].Terminate;
  // Wake up all sleeping threads and keep them awake so they can terminate
  SetEvent(FThreadTerminateEvent);
  // Wait and destroy the threads
****Hangs here -------->      for I := FThreadCount - 1 downto 0 do   
****------->        FThreads[I].Free;
  ReleaseAutoDeleteAsyncCalls;
  // Clean up not yet released AutoDelete InternalAsyncCalls.
  while FAsyncCallHead <> nil do
  begin
    Call := FAsyncCallHead.FNext;
    CheckAutoDelete(FAsyncCallHead);
    FAsyncCallHead := Call;
  end;
  CloseHandle(FThreadTerminateEvent);
  CloseHandle(FWakeUpEvent);
  CloseHandle(FMainThreadSyncEvent);
  DeallocateHWnd(FMainThreadVclHandle);
  DeleteCriticalSection(FAsyncCallsCritSect);
  inherited Destroy;
end;

如果我删除

  for I := FThreadCount - 1 downto 0 do
    FThreads[I].Free;

那么它工作得很好。我无法在一个简单的例子中重现这一点,也想不出任何原因可以在终止时挂起线程。

我相信这与这里提出的问题非常相似:

http://qc.embarcadero.com/wc/qcmain.aspx?d=29843

如果一个线程停止了,它从Classes:ThreadProc和调用threadxitthreadxit使用DLL_THREAD_DETACH调用DLLEntryProc。只有一个进程中的线程可以在DLL中初始化或分离例程一次。

问题是com+ dll的最终化是从DLL_THREAD_DETACH,这样类:等待将等待无限期,因为线程不会终止,因为它无法处理它的DLL_THREAD_DETACH直到结束。

我花了几天时间找出原因,有一个工作是手动执行asyncall单元的结束部分。

这与算法无关,因为即使是非常简单的代码也会在com+ dll中引起问题。

下面是复制的步骤:

创建com+ DLL创建一个包含以下代码的方法:确保TestAsync被调用,当线程池(来自AsyncCall)清理线程时,你会注意到死锁。

procedure Test(int i: integer); cdecl;
begin
  Exit;
end;
procedure TestAsyn;
var
  t: IAsyncCall;
begin
  t := AsyncCall(@Test, [0])
  t.Sync;
end;

看起来你正在尝试做一些你不允许在dll初始化/终结化中做的事情

你需要从其他地方运行那个结束代码。通常这是在dll导出的辅助函数中完成的。另一种选择是在dll的最后一个com对象被释放时运行终结。

导出的函数DllCanUnloadNow看起来像是终止线程的候选函数。

最新更新