【问题标题】:Synchronize() hangs up the threadSynchronize() 挂断线程
【发布时间】:2011-04-15 21:23:27
【问题描述】:

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

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

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

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

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

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

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

请帮忙!

提前谢谢你!!

【问题讨论】:

  • 您使用的是哪个 Delphi 版本? Synchronize 方法的实现(以及答案)基本上取决于版本

标签: multithreading delphi dll synchronize


【解决方案1】:

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

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

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

【讨论】:

  • 感谢您的回答。现在我知道在我的代码中寻找错误是没有意义的:-)。我想知道是否可以使用 SendMessage() 而不是 Synchronize()。我现在无法检查它,所以让我问你你对这个想法的看法。我认为主线程应该处理发送到它创建的窗口的消息,对吧?
  • 顺便说一句,当我在 dll 中创建 TFrame 后代的实例时,是否会出现我提到的那些异常(“控件'...'没有父窗口”)?我做错了什么还是不可能?谢谢!
  • @Mariusz,使用 SendMessage 应该没问题。创建窗口的线程始终是处理发送到该窗口的消息的线程。对于您的第二条评论,请将其作为新的 Stack Overflow 问题发布。这不仅仅是关于同步线程这个问题的后续。
  • 非常感谢。好的,我会在一分钟内添加一个新问题。问候!
【解决方案2】:

使用 Synchronize 是个坏主意,因为它往往会导致这样的竞争条件。我不知道你的代码具体发生了什么——如果没有看到任何代码就很难判断——但这种问题实际上很常见。

线程间通信最好用队列来完成。如果你有最新版本的 Delphi XE,那么 Generics.Collections 中有一个 TThreadedQueue<T> 类非常适合这类事情。将 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'sOmniThreadLibrary)中找到一个。

【讨论】:

  • 我有 BDS 2006 :-/。我正在考虑使用 SendMessage() 而不是 Synchronize()。我明天必须检查一下。无论如何感谢您的回答。我不知道这种队列,它们有时肯定很有用。
  • @Mariusz:既不使用Synchronize() 也不使用SendMessage(),图像的生产者和消费者之间不需要紧密耦合。那么,如果一张图片显示稍晚或根本不显示,因为下一张已经可用,该怎么办?使用PostMessage(),不要将图像存储在消息中(使用真正的线程安全变量)并为每个生产者使用单个图像变量而不是队列。
  • 我在创建 dll 之前已经尝试过了,效果很好。你说得对。我用了一个临界区来同步对图像的访问,线程一直在刷新它,但是只有在主线程已经处理了上一个时才发布消息。我想你是对的。这样更安全;我不必担心应用程序挂起,因为它不会发生。谢谢。
  • 顺便说一句,您如何将异常传递给主线程?我只想通知用户线程中可能发生的问题(例如无法下载图片)。我应该这样做吗?使用 Exception 变量并向接口发布消息,以便它读取它?
【解决方案3】:

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

  1. 在 Dll 表单上放置 TTimer 并在事件调用 CheckSynchronize 上放置 onTimer

procedure TPluginForm.Timer1Timer(Sender: TObject); begin CheckSynchronize; end;

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多