GDI behandelt Leck mit TGIFImage in einem zweiten Thread

Ich habe einen Hintergrund-Thread, der Bilder lädt (entweder von der Festplatte oder von einem Server), mit dem Ziel, sie schließlich zum Zeichnen an den Haupt-Thread weiterzuleiten. Wenn dieser zweite Thread GIF-Bilder mithilfe der VCL lädtTGIFImage Klasse, dieses Programmmanchmal Jedes Mal, wenn die folgende Zeile im Thread ausgeführt wird, verliert sie mehrere Handles:

<code>m_poBitmap32->Assign(poGIFImage);
</code>

Das heißt, das gerade geöffnete GIF-Bild wird einer Bitmap zugewiesen, die dem Thread gehört. Keiner dieser Threads wird mit anderen Threads geteilt, d. H. Sie sind vollständig auf den Thread beschränkt. Es ist zeitabhängig und tritt nicht jedes Mal auf, wenn die Zeile ausgeführt wird, aber wenn es auftritt, geschieht es nur auf dieser Zeile. Jedes Leck besteht aus einem DC, einer Palette und einer Bitmap. (Ich benutzeGDIView, das detailliertere GDI-Informationen liefert als Process Explorer.)m_poBitmap32 hier ist einGraphics32 TBitmap32 Objekt, aber ich habe dies reproduziert mit einfachen VCL-only-Klassen, d. h. mitGraphics::TBitmap::Assign.

Irgendwann bekomme ich eineEOutOfResources Ausnahme, die wahrscheinlich darauf hinweist, dass der Desktop-Heap voll ist:

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

Wie löse ich das und verwende es sicherTGIFImage In einem Hintergrund-Thread?

Und zweitens, werde ich dasselbe Problem mit den PNG-, JPEG- oder BMP-Klassen haben? Ich habe es noch nicht getan, aber da es sich um ein Threading- / Timing-Problem handelt, heißt das nicht, dass ich es nicht tun werde, wenn sie ähnlichen Code verwenden wieTGIFImage.

Ich verwende C ++ Builder 2010 (Teil von RAD Studio.)

Mehr Details

Einige Nachforschungen haben ergebenIch bin nicht die einzige Person, der dies begegnet. Um aus einem Thread zu zitieren,

Help (2007) besagt: In Multithread-Anwendungen, die Lock zum Schutz einer Zeichenfläche verwenden, müssen alle Aufrufe, die die Zeichenfläche verwenden, durch einen Aufruf von Lock geschützt werden. Jeder Thread, der die Zeichenfläche vor der Verwendung nicht sperrt, führt zu potenziellen Fehlern.

[...]

Diese Aussage ist jedoch absolut falsch: Sie MÜSSEN die Zeichenfläche im sekundären Thread sperren, auch wenn andere Threads sie nicht berühren. Andernfalls kann das GDI-Handle der Zeichenfläche im Haupt-Thread jederzeit (asynchron) als nicht verwendet freigegeben werden.

Eine andere Antwort weist auf etwas Ähnliches hin, das möglicherweise mit dem GDI-Objektcache in graphics.pas zusammenhängt.

Das ist beängstigend: Bei einem Objekt, das vollständig in einem Thread erstellt und verwendet wird, können einige seiner Ressourcen asynchron im Haupt-Thread freigegeben werden. Unglücklicherweise,Ich weiß nicht, wie ich den Lock-Rat anwenden sollTGIFImage. TGIFImage hat keinCanvas, obwohl es eine hatBitmap das hat eine Leinwand. Das Sperren hat keine Auswirkung. Ich vermute, dass das Problem tatsächlich darin liegtTGIFFrame, eine interne Klasse. Ich weiß auch nicht, ob oder wie ich TBitmap32-Ressourcen sperren soll. Ich habe versucht, eine zuzuweisenTMemoryBackend auf die Bitmap, die die Verwendung von GDI vermeidet, aber keine Wirkung hatte.

Reproduktion

Sie können dies sehr einfach reproduzieren. Erstellen Sie eine neue VCL-App und erstellen Sie eine neue Einheit, die einen Thread enthält. Platzieren Sie in der Execute-Methode des Threads den folgenden Code:

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

Sie können verwendenGraphics::TBitmap wenn Sie Graphics32 nicht installiert haben.

Fügen Sie im Hauptformular der App eine Schaltfläche hinzu, mit der der Thread erstellt und gestartet wird. Fügen Sie eine weitere Schaltfläche hinzu, die ähnlichen Code wie oben ausführt (nur einmal, es ist keine Schleife erforderlich. Mine speichert TBitmap32 auch als Elementvariable, anstatt es dort zu erstellen, und macht es ungültig, damit es schließlich im Formular angezeigt wird.) Führen Sie das Programm aus und klicken Sie auf die Schaltfläche, um den Thread zu starten. Sie werden wahrscheinlich sehen, dass GDI-Objekte bereits lecken, aber wenn Sie nicht die zweite Taste drücken, die den ähnlichen Code einmal im Haupt-Thread ausführt - sobald dies ausreicht, scheint es etwas auszulösen - und es wird lecken. Sie werden feststellen, dass die Speichernutzung zunimmt und dass GDI-Handles mit einer Geschwindigkeit von mehreren Dutzend pro Sekunde verloren gehen.

Antworten auf die Frage(1)

Ihre Antwort auf die Frage