Delphi - 帮助从另一个线程调用线程dll函数

时间:2010-01-22 04:12:00

标签: multithreading delphi dll synchronize

我正在使用Delphi 2006,并且我正在开发的应用程序存在一些问题。

我有一个创建线程的表单,该线程调用执行冗长操作的函数,我们称之为LengthyProcess。在LengthyProcess函数内部,我们还调用了几个Dll函数,这些函数也创建了自己的线程。

我遇到的问题是,如果我不使用我的线程的Synchronize函数调用LengthyProcess,则线程停止响应(主线程仍然响应正常)。我不想使用Synchronize,因为这意味着主线程正在等待LengthyProcess完成,因此无法创建单独的线程。

我已经将问题跟踪到创建线程的dll内的函数,然后调用WaitFor,顺便说一下,这都是使用TThread完成的。 WaitFor检查CurrentThreadID是否等于MainThreadID,如果是,那么它将调用CheckSychronization,一切正常。因此,如果我们使用Synchronize,则CurrentThreadID将等于MainThreadID,但是如果我们不使用Synchronize,那么当然CurrentThreadID<> MainThreadID,当发生这种情况时,WaitFor告诉当前线程(我创建的线程)等待DLL创建的线程,因此CheckSynchronization永远不会被调用,我的线程最终会等待dll中创建的线程。

我希望这是有道理的,抱歉,我不知道有什么更好的解释方法。有没有其他人有这个问题,知道如何解决它吗?

3 个答案:

答案 0 :(得分:7)

如果您的辅助线程“停止响应”,那么我认为它有一个消息泵。 (否则,您需要解释它停止响应的内容。)您似乎也希望线程能够检测到第三个线程何时完成运行。 (这里的“主要”线程是VCL线程,根本不涉及。)

您尝试使用WaitFor,但在发现它阻止时感到失望。不过,这就是它一直以来的设计目标。它在主线程中的行为是奇怪的,所以从VCL线程调用是安全的,即使它原本从来没有真正意义上使用过。

要处理消息等待线程完成运行,您需要使用Windows API中的一个或多个wait functions。从MsgWaitForMultipleObjects开始。它可以等待各种类型的内核句柄,包括线程句柄,但也可以在消息可用时通知您。这个想法是你将循环调用该函数。当它说消息可用时,处理它们,然后再次循环以继续等待。

以下只是一个大纲。您需要查看所有API函数的文档,并将其与您对自己的线程的其他知识相结合。

procedure TSecondaryThread.Execute;
var
  ret: DWord;
  ThreadHandle: THandle;
  Msg: TMsg;
begin
  ThreadHandle := TertiaryThread.Handle;
  repeat
    ret := MsgWaitForMultipleObjects(1, ThreadHandle, False, Infinite, qs_AllEvents);
    case ret of
      Wait_Object_0: begin
        // The thread terminated. Do something about it.
        CloseHandle(ThreadHandle);
        PostQuitMessage(0);
        // Put *something* in the parameter so further calls to MWFMO
        // will have a valid handle. May as well use a handle to something
        // that will never become signaled so all we'll get are more
        // messages. I'm pretty sure you can't pass an empty array of
        // handles; there must be at least one, and it must be valid.
        ThreadHandle := Self.Handle;
      end;
      Wait_Object_0 + 1: begin
        // At least one message is available. Handle *all* of
        // them before calling MsgWaitForMultipleObjects again
        while PeekMessage(Msg, 0, 0, 0, pm_Remove) do
        case Msg.Message of
          wm_Quit: begin
            // Do something about terminating the tertiary thread.
            // Then stop the message loop and the waiting loop.
            Exit;
          end;
          else begin
            TranslateMessage(Msg);
            DispatchMessage(Msg);
          end;
        end;
      end;
      Wait_Timeout: Assert(False, 'Infinity has passed');
      Wait_Failed: RaiseLastOSError;
      else Assert(False, 'Unexpected return value');
    end;
  until False;
end;

关于处理所有消息的部分非常重要。只要您拨打GetMessagePeekMessageWaitMessage,操作系统就会将队列中的所有邮件标记为“旧”,但MsgWaitForMultipleObjects只会在有“队列中的“新”消息 - 在最后一次调用PeekMessage后到达的消息。

答案 1 :(得分:0)

嗨,谢谢你的回复,是的,我知道我的问题不是很清楚,有点令人困惑;所以我会尝试澄清一些事情,这里就是......

下面描述的所有主题都来自TThread。

我有一个启动线程但不等待它的表单。由表单启动的线程调用执行长任务的函数。

该函数调用DLL中的另一个函数,DLL中的函数启动一个线程并等待它。 DLL函数启动的线程通过同步调用另一个函数。

Form->启动一个线程但不等待 - >线程调用函数 - >该函数调用DLL函数 - > Dll函数启动一个线程并等待 - >该线程由DLL函数通过同步调用另一个函数,即Synchronize(UpdateRecords)。

问题是对同步的调用永远不会返回,因为从我所看到的,它已经进入某种死锁。

同步的工作原理:Synchronize将方法调用放入队列并设置事件,然后Synchronize等待事件发出信号。当主线程处于空闲状态时,它将处理在队列中等待的方法调用,在处理完方法调用之后,它将发出相关事件的信号,以便启动同步的线程可以继续。

由表单启动的线程不使用synchronize来调用执行long任务的函数,如果它确实使用了synchronized,那么应用程序没有死锁,但是这样做会打破长时间使用线程的目的过程

我已经找到了问题,似乎是由dll创建的TApplication对象没有处理消息并且句柄为0,这是怎么回事我不知道(我没有写DLL ,它是由其他人写的),但这是问题的原因,因为它永远不会处理同步排队的方法。

我之前提到过,如果我使用synchronize调用从我的线程执行长进程的函数,那么应用程序不会死锁。这是因为主线程将负责调用执行long进程的函数。因此,长进程函数调用DLL函数,该函数启动另一个线程,然后调用WaitFor。 WaitFor检查当前线程是否是主线程,如果是,则它处理已经通过同步排队的方法调用,连续循环,直到它等待的线程被释放的线程(即方法)通过同步排队进行调用并发出等待事件信号。

在WaitFor中,如果当前线程不是主线程,那么WaitFor只会阻塞,直到它正在等待的线程被释放。

无论如何,我对dll中的应用程序对象无能为力,因为该DLL非常复杂并且被更大的系统使用。我想我可以在dll中公开一个可以处理同步队列中的方法的方法,然后我可以在我的应用程序空闲时调用此方法。

无论如何,再次感谢你的帮助,但我现在已经解决了这个问题。

答案 2 :(得分:0)

在Delphi DLL中使用TThread类甚至Application对象非常不安全。 RTL和VCL核心类,全局变量和单例对象被设计为每个进程存在一次,并且无法处理标准DLL库中的命名空间重复和不完整初始化。
您可以通过构建运行时包(RTL和VCL就足够了;您也可以只使用您需要的系统单元构建自己的包),在EXE和所有引用运行时单元的DLL(表单和类,尤其是) - 它们以这种方式获得单个共享命名空间和完整的EXE初始化序列 如果您根本无法修改DLL,可以尝试将其Application.HandleMainThreadIDSyncEventWakeMainThread设置为主EXE模块中的相应值 - 这个可能工作,但它看起来就像它看起来一样丑陋,并没有涵盖所有边缘情况(类和重要的全局变量仍然会重复)。