GDI 在第二个线程中使用 TGIFImage 处理泄漏 [英] GDI handle leak using TGIFImage in a second thread

查看:23
本文介绍了GDI 在第二个线程中使用 TGIFImage 处理泄漏的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个后台线程加载图像(从磁盘或服务器),目标是最终将它们传递给主线程进行绘制.当第二个线程使用 VCL 的 TGIFImage加载 GIF 图像时,这个程序有时在线程中每次执行以下行时会泄漏几个句柄:

I have a background thread which loads images (either from disk or a server), with the goal of eventually passing them to the main thread to draw. When this second thread is loading GIF images using the VCL's TGIFImage class, this program sometimes leaks several handles each time the following line executes in the thread:

m_poBitmap32->Assign(poGIFImage);

也就是说,刚打开的 GIF 图像正在分配给线程拥有的位图.这些都不与任何其他线程共享,即完全本地化到线程.它与时间有关,因此不会在每次执行该行时都发生,但是当它确实发生时,它只会在该行上发生.每个泄漏是一个 DC、一个调色板和一个位图.(我使用 GDIView,它提供了比 Process Explorer 更详细的 GDI 信息.)m_poBitmap32这是一个 Graphics32 TBitmap32 对象,但我使用纯 VCL-only 类复制了它,即使用 Graphics::TBitmap::赋值.

That is, the just-opened GIF image is being assigned to a bitmap owned by the thread. None of these are shared with any other threads, i.e. are entirely localised to the thread. It is timing-dependent, so doesn't occur every time the line is executed, but when it does occur it happens only on that line. Each leak is one DC, one palette, and one bitmap. (I use GDIView, which gives more detailed GDI information than Process Explorer.) m_poBitmap32 here is a Graphics32 TBitmap32 object, but I have reproduced this using plain VCL-only classes, i.e. using Graphics::TBitmap::Assign.

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

Eventually I get an EOutOfResources exception, probably indicating the desktop heap is full:

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

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

其次,我会在使用 PNG、JPEG 或 BMP 类时遇到同样的问题吗?到目前为止我还没有,但鉴于这是一个线程/计时问题,这并不意味着我不会使用与 TGIFImage 类似的代码.

And secondly, will I encounter this same problem with the PNG, JPEG or BMP classes? I haven't so far, but given it's a threading / timing issue that doesn't mean I won't if they use similar code to TGIFImage.

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

I am using C++ Builder 2010 (part of RAD Studio.)

一些研究表明我不是唯一遇到这种情况的人.引用一个线程,

Some research showed I'm not the only person to encounter this. To quote from one thread,

帮助 (2007) 说:在使用 Lock 保护画布的多线程应用程序中,所有使用画布的调用都必须通过对锁.任何在使用之前不锁定画布的线程都会引入潜在的错误.

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.

[...]

但是这种说法是绝对错误的:您必须将画布锁定在次线程即使其他线程不接触它.否则画布的 GDI 句柄可以在主线程中释放为未使用的任何时刻(异步).

另一个回复表明了类似的情况,可能与 graphics.pas 中的 GDI 对象缓存有关.

Another reply indicates something similar, that it may be to do with the GDI object cache in graphics.pas.

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

That's scary: an object created and used entirely in one thread can have some of its resources freed asynchronously in the main thread. Unfortunately, I don't know how to apply the Lock advice to TGIFImage. TGIFImage has no Canvas, although it does have a Bitmap which has a canvas. Locking that has no effect. I suspect that the problem is actually in TGIFFrame, an internal class. I also do not know if or how I should lock any TBitmap32 resources. I did try assigning a TMemoryBackend to the bitmap, which avoids using GDI, but it had no effect.

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

You can reproduce this very easily. Create a new VCL app, and make a new unit which contains a thread. In the thread's Execute method, place this code:

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.

You can use Graphics::TBitmap if you don't have Graphics32 installed.

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

In the app's main form, add a button which creates and starts the thread. Add another button which executes similar code to the above (once only, no need to loop. Mine also stores the TBitmap32 as a member variable instead of creating it there, and invalidates so it will eventually paint it to the form.) Run the program and click the button to start the thread. You will probably see GDI objects leak already, but if not press the second button which runs the similar code once in the main thread - once is enough, it seems to trigger something - and it will leak. You will see memory usage rise, and that it leaks GDI handles at the rate of several dozen per second.

推荐答案

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

Unfortunately, the fix is very, very ugly. The basic idea is that the background thread must acquire a lock that the main thread holds when it's between messages.

幼稚的实现是这样的:

  1. 锁定画布互斥锁.
  2. 生成后台线程.
  3. 等待消息.
  4. 释放画布互斥锁.
  5. 处理消息.
  6. 锁定画布互斥锁.
  7. 转到第 3 步.

请注意,这意味着后台线程只能在主线程忙时访问 GDI 对象,而不能在等待消息时访问.这意味着后台线程在不持有互斥锁时不能拥有任何画布.这两个要求往往太痛苦了.所以你可能需要改进算法.

Note that this means the background thread can only access GDI objects while the main thread is busy, not while it's waiting for a message. And this means the background thread cannot own any canvasses while it does not hold the mutex. These two requirements tend to be too painful. So you may need to refine the algorithm.

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

One refinement is to have the background thread send the main thread a message when it needs to use a canvas. This will cause the main thread to more quickly release the canvas mutex so the background thread can get it.

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

I think this will be enough to make you give up this idea. Instead, perhaps, read the file from the background thread but process it in the main thread.

这篇关于GDI 在第二个线程中使用 TGIFImage 处理泄漏的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆