画像を(ディスクまたはサーバーから)ロードするバックグラウンドスレッドがあり、最終的にそれらをメインスレッドに渡して描画することを目標としています。この 2 番目のスレッドが VCL のTGIFImage
クラスを使用して GIF 画像をロードしている場合、このプログラムは、次の行がスレッドで実行されるたびにいくつかのハンドルをリークすることがあります。
m_poBitmap32->Assign(poGIFImage);
つまり、開いたばかりの GIF イメージは、スレッドが所有するビットマップに割り当てられています。これらはいずれも他のスレッドと共有されません。つまり、スレッドに完全にローカライズされます。これはタイミングに依存するため、行が実行されるたびに発生するわけではありませんが、発生するとその行でのみ発生します。各リークは、1 つの DC、1 つのパレット、および 1 つのビットマップです。( Process Explorer よりも詳細な GDI 情報を提供 するGDIViewm_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 の一部) を使用しています。
詳細
いくつかの調査によると、これに遭遇したのは私だけではありません。あるスレッドから引用すると、
Help (2007) によると: Lock を使用してキャンバスを保護するマルチスレッド アプリケーションでは、キャンバスを使用するすべての呼び出しを Lock の呼び出しで保護する必要があります。使用前にキャンバスをロックしないスレッドは、潜在的なバグをもたらします。
[...]
ただし、このステートメントは絶対に誤りです。他のスレッドがキャンバスに触れていなくても、キャンバスをセカンダリ スレッドでロックする必要があります。それ以外の場合、キャンバスの GDI ハンドルは、メイン スレッドで未使用としていつでも (非同期に) 解放できます。
別の回答は、graphics.pas の GDI オブジェクト キャッシュに関係している可能性があることを示しています。
これは恐ろしいことです。1 つのスレッドで完全に作成および使用されるオブジェクトは、そのリソースの一部をメイン スレッドで非同期に解放することができます。残念ながら、Lock アドバイスを に適用する方法がわかりませんTGIFImage
。 TGIFImage
にはありませんCanvas
がBitmap
、キャンバスを持つ があります。効果のないロック。TGIFFrame
問題は実際には内部クラスであると思われます。また、TBitmap32 リソースをロックする必要があるかどうか、またはロックする方法もわかりません。TMemoryBackend
GDIの使用を回避するビットマップにaを割り当てようとしましたが、効果はありませんでした。
再生
これは非常に簡単に再現できます。新しい 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;
}
}
Graphics::TBitmap
Graphics32 がインストールされていない場合に使用できます。
アプリのメイン フォームに、スレッドを作成して開始するボタンを追加します。上記と同様のコードを実行する別のボタンを追加します (1 回だけ、ループする必要はありません。私の場合は、TBitmap32 を作成する代わりにメンバー変数として格納し、最終的にフォームにペイントするように無効にします)。プログラムを実行します。ボタンをクリックしてスレッドを開始します。おそらく、GDI オブジェクトが既にリークしているのを目にするでしょうが、メイン スレッドで同様のコードを 1 回実行する 2 番目のボタンを押さないと (1 回で十分です。何かをトリガーしているように見えます)、リークが発生します。メモリ使用量が増加し、GDI ハンドルが毎秒数十の割合でリークすることがわかります。