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

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

问题描述

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

  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的一部分)。






更多细节



有些研究显示我'不是唯一遇到这个的人。从一个线程引用


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



[...]



但是这个语句是绝对的错误:即使其他线程没有触摸它,你也必须将画布锁定在
辅助线程中。否则,
canvas的GDI句柄可以在任何
时刻(异步地)被释放到主线程中。


另一个答复表示类似的东西,可能与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(Ltest.gif);
poBMP32-> Assign(poGraphic);
} __finally {
delete poBMP32;
删除poGraphic;
}
}

您可以使用图形: :TBitmap 如果您没有安装Graphics32。



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

解决方案

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



初始化实现如下:


  1. 锁定画布互斥。

  2. 生成后台线程。

  3. 等待消息。

  4. 发布画布互斥体。

  5. 处理消息。

  6. $ b
  7. 转到步骤3。

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



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



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


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);

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.

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

: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)

How do I solve this and safely use TGIFImage in a background thread?

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.

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


More details

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

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).

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

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.

Reproduction

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;
    }
}

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

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.

The naive implementation is like this:

  1. Lock canvas mutex.
  2. Spawn background thread.
  3. Wait for message.
  4. Release canvas mutex.
  5. Process message.
  6. Lock canvas mutex.
  7. Go to step 3.

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天全站免登陆