Synchronize()挂起线程

时间:2010-09-22 17:56:05

标签: multithreading delphi dll synchronize

我正在Delphi中编写一个dll库,它创建了多个线程。让我一步一步地描述问题。我很抱歉提前做了冗长的描述: - (。

让我们暂时忘掉图书馆。我创建了一个Windows应用程序,它将从几个摄像头呈现视图。我创建了一个窗口,用于显示来自单个摄像头的视图,它包含一个TImage控件。有一个线程(一个TThread后代)每隔几毫秒从摄像机下载当前图像,并将其分配给该窗口的TImage控件(使用Synchronize()方法)。应用程序在启动时创建该窗口的多个实例(每个窗口都有一个单独的线程),因此您可以同时从多个摄像头中查看实时视图。更重要的是,所有这些查看窗口都是主应用程序窗口的父级,因此它们出现在其中。

一切正常,直到我决定将这两个窗口放入dll库中。我发现有必要出于某些原因,但现在它们并不重要。所以我创建了一个新的dll库,将现有的主窗口和摄像机视图窗口添加到项目中,并导出了一个创建并返回主窗口实例的函数。创建主窗口时,它会创建几个摄像机视图窗口,使其成为父窗口。

然后,出于测试目的,我创建了一个应用程序,它从库中导入上面提到的dll函数,并在启动时调用它来获取主窗口的实例;然后只是在屏幕上显示它(处于非模态状态)。

当我启动应用程序时,我发现我无法从任何相机中获取单个图像。当我调试它时,我注意到当线程调用Synchronize()方法时,它会永远挂起。在将这两个窗口放入dll之前没有发生过。

这是我的问题。说实话,这是我对图书馆的第一种方法,到目前为止,我必须解决许多其他问题。您可能想知道为什么我使用Windows而不是框架...所以每当我在dll中创建一个TFrame实例时,我会得到一个例外,说“控件xxx没有父窗口”。我不知道该怎么办,所以我改用了windows: - (。

您能否告诉我如何处理同步问题?当应用程序启动时,主线程似乎没有以任何方式被阻止它接受点击按钮等。那么问题是什么?

请帮忙!

提前谢谢!!

3 个答案:

答案 0 :(得分:13)

当您调用TThread.Synchronize时,线程和方法指针将添加到Classes.pas中的全局SyncList: TList。在主exe的TApplication.Idle例程调用CheckSynchronize中,它检查SyncList,但它将检查exe中的版本而不是DLL中的版本。最终结果,永远不会调用您的同步方法。

最简单的解决方法是从DLL切换到包,这将消除重复的SyncList

另一种方法是覆盖exe的Application.OnIdle回调,并手动调用DLL的CheckSynchronize。你需要一些来自应用程序的帮助,因为你的DLL也会有一个Application对象,而且那个对象不起作用。

答案 1 :(得分:2)

使用Synchronize是一个坏主意,因为它往往会导致像这样的竞争条件。我不知道你的代码中有什么特别的东西 - 没有看到任何代码就很难说 - 但实际上这种问题很常见。

使用队列可以更好地完成线程间通信。如果您有最新版本的Delphi XE,那么TThreadedQueue<T>中的Generics.Collections类就是这种类型的理想选择。将0传递给构造函数中的PopTimeout参数,让相机线程推送图像,让主线程轮询第三个PopItem重载的队列,如下所示:

var
  CurrentItem: TImage;
begin
  if ThreadQueue.PopItem(CurrentItem) = wrSignaled then
    UpdateImage(CurrentItem); //or however you do it
end;

(如果队列中没有任何内容,PopItem将返回wrTimeout。)

如果你没有Delphi XE,你需要建立自己的线程安全队列,或者从第三方来源找到一个,例如Primoz Gabrielcic's OmniThreadLibrary。

答案 2 :(得分:0)

我找到了两种解决Synchronize()挂起线程的方法(在Delphi 7中):

  1. 在Dll表单上放置TTimeronTimer事件调用CheckSynchronize;
  2. procedure TPluginForm.Timer1Timer(Sender: TObject); begin CheckSynchronize; end;

    1. Add this module to uses section of the Dll form