delphi – GDI在第二个线程中使用TGIFImage处理泄漏

前端之家收集整理的这篇文章主要介绍了delphi – GDI在第二个线程中使用TGIFImage处理泄漏前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
我有一个后台线程加载图像(从磁盘或服务器),目的是最终将它们传递到主线程来绘制。当第二个线程使用VCL的 TGIFImage class加载GIF图像时,该程序有时每次在线程中执行以下行时会泄漏几个句柄:
m_poBitmap32->Assign(poGIFImage);

也就是说,刚刚打开的GIF图像被分配给线程拥有的位图。这些都不与任何其他线程共享,即完全定位到线程。它是时序相关的,所以每次执行时都不会发生,但是当它发生时,它只发生在该行上。每个泄漏都是一个DC,一个调色板和一个位图。 (我使用GDIView,它提供比Process Explorer更详细的GDI信息。)m_poBitmap32这里是一个Graphics32 TBitmap32对象,但是我已经使用纯VCL专用类,即使用Graphics :: TBitmap :: Assign来再现这个对象。

最终我得到一个EOutOfResources异常,可能表示桌面堆已满:

:7671b9bc KERNELBASE.RaiseException + 0x58
:40837f2f ; C:\Windows\SysWOW64\vclimg140.bpl
:40837f68 ; C:\Windows\SysWOW64\vclimg140.bpl
:4084459f ; C:\Windows\SysWOW64\vclimg140.bpl
:4084441a vclimg140.@Gifimg@TGIFFrame@Draw$qqrp16Graphics@TCanvasrx11Types@TRectoo + 0x4a
:408495e2 ; C:\Windows\SysWOW64\vclimg140.bpl
:50065465 rtl140.@Classes@TPersistent@Assign$qqrp19Classes@TPersistent + 0x9
:00401C0E TLoadingThread::Execute(this=:00A44970)

后台线程中如何解决这个问题并安全使用TGIFImage?

其次,我会遇到与PNG,JPEG或BMP类相同的问题吗?我还没有到目前为止,但是考虑到这是一个线程/时序问题,这并不意味着我不会,如果他们使用相似的代码到TGIFImage。

我使用C Builder 2010(RAD Studio的一部分)

更多细节

一些研究显示I’m not the only person to encounter this.从一个线索引用,

Help (2007) says:
In multi-threaded applications that use Lock to protect a canvas,all calls that use the canvas must be protected by a call to
Lock. Any thread that does not lock the canvas before using it will
introduce potential bugs.

[…]

But this statement is absolute false: you MUST lock the canvas in
secondary thread even if other threads don’t touch it. Otherwise the
canvas’s GDI handle can be freed in main thread as unused at any
moment (asynchronously).

另一个回复表示类似的东西,它可能与graphics.pas中的GDI对象缓存有关。

这是可怕的:一个完全在一个线程中创建和使用的对象可以使其某些资源在主线程中异步释放。不幸的是,我不知道如何将锁定建议应用于TGIFImage。 TGIFImage没有Canvas,尽管它有一个Bitmap,它有一个画布。锁定没有效果。我怀疑这个问题其实是TGIFFrame,一个内部的类。我也不知道我应该如何锁定任何TBitmap32资源。我确实尝试将TMemoryBackend分配给位图,避免使用GDI,但它没有任何效果

再生产

你可以很容易地重现。创建一个新的VCL应用程序,并创建一个包含线程的新单元。在线程的Execute方法中,放置这个代码

while (!Terminated) {
    TGraphic* poGraphic = new TGIFImage();
    TBitmap32* poBMP32 = new TBitmap32();
    __try {
        poGraphic->LoadFromFile(L"test.gif");
        poBMP32->Assign(poGraphic);
    } __finally {
        delete poBMP32;
        delete poGraphic;
    }
}

如果您没有安装Graphics32,可以使用Graphics :: TBitmap。

在应用程序的主窗体中,添加一个创建和启动线程的按钮。添加另一个执行类似代码的按钮(只需一次,不需要循环),Mine还将TBitmap32存储为成员变量,而不是在其中创建,并且无效,因此最终将其绘制到表单中。)运行程序并点击按钮启动线程。你可能会看到GDI对象已经泄漏了,但是如果没有按下在主线程中运行相似代码的第​​二个按钮,一次就足够了,它似乎触发了一些东西,并且会泄漏。您将看到内存使用率上升,并且以每秒几十的速率泄漏GDI句柄。

解决方法

不幸的是,修复非常非常丑陋。基本思想是后台线程必须获取主线程在消息之间持有的锁。

天真的实现是这样的:

>锁定画布互斥。
>生成后台线程。
>等待消息。
>发布画布互斥体。
>处理信息。
>锁定画布互斥。
>转到步骤3。

请注意,这意味着当主线程忙时,后台线程只能访问GDI对象,而不是等待消息。这意味着后台线程不能拥有任何拉票,而不保留互斥体。这两个要求往往太痛苦了。所以你可能需要改进算法。

一个改进是让后台线程在需要使用画布时发送主线程消息。这将导致主线程更快地释放画布互斥体,以便后台线程可以得到它。

我认为这将足以让你放弃这个想法。相反,也许,从后台线程读取文件,但在主线程中处理它。

猜你在找的Delphi相关文章