1

我正在使用 Delphi 2006,但我正在开发的应用程序有点问题。

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

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

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

我希望这是有道理的,对不起,我不知道有什么更好的方法来解释它。有没有其他人遇到过这个问题并且知道如何解决它?

4

3 回答 3

7

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

您尝试使用WaitFor,但当您发现它阻塞时感到很失望。不过,这就是它一直设计的目的。它在主线程中的行为很奇怪,因此从 VCL 线程调用是安全的,即使它最初从未真正打算以这种方式使用。

要处理消息等待线程完成运行,您需要使用 Windows API 中的一个或多个等待函数。开始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;

处理所有消息的部分很重要。只要您调用GetMessage,PeekMessageWaitMessage,操作系统就会将队列中的所有消息标记为“旧”消息,但MsgWaitForMultipleObjects只有在队列中有“新”消息时才会返回 - 该消息是在最后一次调用之后PeekMessage到达的。

于 2010-01-22T05:54:01.217 回答
0

嗨,感谢您的回复,是的,我意识到我的问题不是很清楚而且有些混乱;所以我会试着澄清一下,这里是..

下面描述的所有线程都是从 TThread 派生的。

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

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

窗体->启动线程但不等待->线程调用函数->函数调用DLL函数->Dll函数启动线程并等待->DLL函数启动的线程通过同步调用另一个函数即同步(更新记录)。

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

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

表单启动的线程不使用 synchronize 来调用执行长任务的函数,如果确实使用了 synchronize 则应用程序不会死锁,但这违背了使用线程进行长进程的目的。

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

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

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

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

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

于 2010-01-24T20:59:36.660 回答
0

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

于 2010-06-01T00:25:07.833 回答