Direct2D-Multithread-Apps
Wenn Sie Direct2D-Apps entwickeln, müssen Sie möglicherweise von mehreren Threads aus auf Direct2D-Ressourcen zugreifen. In anderen Fällen können Sie Multithreading verwenden, um eine bessere Leistung oder Reaktionsfähigkeit zu erzielen (z. B. die Verwendung eines Threads für die Bildschirmanzeige und eines separaten Threads für das Offlinerendering).
In diesem Thema werden die bewährten Methoden für die Entwicklung von Multithread-Direct2D-Apps mit wenig bis gar keinem Direct3D-Rendering beschrieben. Softwarefehler, die durch Parallelitätsprobleme verursacht werden, können schwierig nachverfolgt werden, und es ist hilfreich, Ihre Multithreadingrichtlinie zu planen und die hier beschriebenen bewährten Methoden zu befolgen.
Hinweis
Wenn Sie auf zwei Direct2D-Ressourcen zugreifen, die aus zwei verschiedenen Singlethread-Direct2D-Factorys erstellt wurden, führt dies nicht zu Zugriffskonflikten, solange die zugrunde liegenden Direct3D-Geräte und Gerätekontexte ebenfalls unterschiedlich sind. Wenn in diesem Artikel von "Zugriff auf Direct2D-Ressourcen" gesprochen wird, bedeutet dies tatsächlich "Zugriff auf Direct2D-Ressourcen, die über dasselbe Direct2D-Gerät erstellt wurden", sofern nichts anderes angegeben ist.
Entwickeln Thread-Safe Apps, die nur Direct2D-APIs aufrufen
Sie können eine Multithread-Direct2D-Factoryinstanz erstellen. Sie können eine Multithreadfactory und alle zugehörigen Ressourcen aus mehr als einem Thread verwenden und freigeben. Der Zugriff auf diese Ressourcen (über Direct2D-Aufrufe) wird jedoch von Direct2D serialisiert, sodass keine Zugriffskonflikte auftreten. Wenn Ihre App nur Direct2D-APIs aufruft, wird dieser Schutz automatisch von Direct2D auf einer präzisen Ebene mit minimalem Mehraufwand durchgeführt. Der Code zum Erstellen einer Multithreadfactory hier.
ID2D1Factory* m_D2DFactory;
// Create a Direct2D factory.
HRESULT hr = D2D1CreateFactory(
D2D1_FACTORY_TYPE_MULTI_THREADED,
&m_D2DFactory
);
Die Abbildung hier zeigt, wie Direct2D zwei Threads serialisiert, die Aufrufe nur mithilfe der Direct2D-API durchführen.

Entwickeln Thread-Safe Direct2D-Apps mit minimalen Direct3D- oder DXGI-Aufrufen
Es ist häufiger, dass eine Direct2D-App auch einige Direct3D- oder DXGI-Aufrufe vornimmt. Beispielsweise wird ein Anzeigethread in Direct2D gezeichnet und dann mithilfe einer DXGI-Swapkettedargestellt.
In diesem Fall ist die Threadsicherheit komplizierter: Einige Direct2D-Aufrufe greifen indirekt auf die zugrunde liegenden Direct3D-Ressourcen zu, auf die möglicherweise gleichzeitig von einem anderen Thread zugegriffen wird, der Direct3D oder DXGI aufruft. Da diese Direct3D- oder DXGI-Aufrufe nicht von Direct2D bekannt und gesteuert werden, müssen Sie eine Multithread-Direct2D-Factory erstellen, aber Sie müssen versuchen, Zugriffskonflikte zu vermeiden.
Das folgende Diagramm zeigt einen Direct3D-Ressourcenzugriffskonflikt, weil thread T0 indirekt über einen Direct2D-Aufruf auf eine Ressource zugreift und T2 direkt über einen Direct3D- oder DXGI-Aufruf auf die gleiche Ressource zugreift.
Hinweis
Der Threadschutz, den Direct2D bereitstellt (die blaue Sperre in diesem Bild), ist in diesem Fall nicht hilfreich.

Zur Vermeidung von Ressourcenzugriffskonflikten empfiehlt es sich, explizit die Sperre zu erhalten, die Direct2D für die interne Zugriffssynchronisierung verwendet, und diese Sperre anzuwenden, wenn ein Thread Direct3D- oder DXGI-Aufrufe ausführen muss, die zu Zugriffskonflikten führen können, wie hier gezeigt. Insbesondere sollten Sie bei Code, der Ausnahmen oder ein Early Out-System verwendet, das auf HRESULT-Rückgabecodes basiert, besondere Vorsicht walten lassen. Aus diesem Grund wird empfohlen, ein RAII-Muster (Resource Acquisition Is Initialization) zu verwenden, um die Enter- und Leave-Methoden aufzurufen.
Hinweis
Es ist wichtig, dass Sie Aufrufe der Enter-Methode und der Leave-Methode koppeln, da andernfalls ihre App zu Deadlocks führen kann.
Der Code hier zeigt ein Beispiel dafür, wann Direct3D- oder DXGI-Aufrufe gesperrt und anschließend entsperrt werden müssen.
void MyApp::DrawFromThread2()
{
// We are accessing Direct3D resources directly without Direct2D's knowledge, so we
// must manually acquire and apply the Direct2D factory lock.
ID2D1Multithread* m_D2DMultithread;
m_D2DFactory->QueryInterface(IID_PPV_ARGS(&m_D2DMultithread));
m_D2DMultithread->Enter();
// Now it is safe to make Direct3D/DXGI calls, such as IDXGISwapChain::Present
MakeDirect3DCalls();
// It is absolutely critical that the factory lock be released upon
// exiting this function, or else any consequent Direct2D calls will be blocked.
m_D2DMultithread->Leave();
}
Hinweis
Einige Direct3D- oder DXGI-Aufrufe (insbesondere IDXGISwapChain::P resent)können Sperren abrufen und/oder Rückrufe im Code der aufrufenden Funktion oder Methode auslösen. Dies sollten Sie beachten und sicherstellen, dass ein solches Verhalten keine Deadlocks verursacht. Weitere Informationen finden Sie im Thema DXGI Overview .

Wenn Sie die Enter- und Leave-Methoden verwenden, werden die Aufrufe durch die automatische Direct2D-Sperre und die explizite Sperre geschützt, sodass für die App kein Zugriffskonflikt vorkommt.
Es gibt andere Ansätze, um dieses Problem zu umgehen. Es wird jedoch empfohlen, Direct3D- oder DXGI-Aufrufe explizit mit der Direct2D-Sperre zu schützen, da sie in der Regel eine bessere Leistung bietet, da die Parallelität auf einer viel feineren Ebene und mit geringerem Mehraufwand unter der Abdeckung von Direct2D geschützt wird.
Sicherstellen der Atomarität zustandsvoller Vorgänge
Threadsicherheitsfeatures von DirectX können zwar dazu beitragen, sicherzustellen, dass keine zwei einzelnen API-Aufrufe gleichzeitig erfolgen, Sie müssen aber auch sicherstellen, dass Threads, die zustandsbehaftete API-Aufrufe durchführen, nicht miteinander beeinträchtigen. Beispiel:
- Es gibt zwei Textzeilen, die Sie sowohl auf dem Bildschirm (von Thread 0) als auch außerhalb des Bildschirms (von Thread 1) rendern möchten: Zeile # 1 ist "A ist größer" und Zeile # 2 ist "als B", die beide mit einem soliden schwarzen Pinsel gezeichnet werden.
- Thread 1 zeichnet die erste Textzeile.
- Thread 0 reagiert auf eine Benutzereingabe, aktualisiert beide Textzeilen auf "B ist kleiner" bzw. "als A" und ändert die Pinselfarbe für seine eigene Zeichnung in Volltonrot.
- Thread 1 setzt das Zeichnen der zweiten Textzeile ( jetzt "als A" ) mit dem roten Farbpinsel fort.
- Schließlich erhalten wir zwei Textzeilen auf dem Zeichenziel außerhalb des Bildschirms: "A ist größer" in Schwarz und "als A" rot.

In der oberen Zeile zeichnet Thread 0 mit aktuellen Textzeichenfolgen und dem aktuellen schwarzen Pinsel. Thread 1 beendet nur das Zeichnen außerhalb des Bildschirms in der oberen Hälfte.
In der mittleren Zeile antwortet Thread 0 auf benutzerinteraktion, aktualisiert die Textzeichenfolgen und den Pinsel und aktualisiert dann den Bildschirm. An diesem Punkt wird Thread 1 blockiert. In der unteren Zeile setzt das endgültige Rendering außerhalb des Bildschirms nach Thread 1 das Zeichnen der unteren Hälfte mit einem geänderten Pinsel und einer geänderten Textzeichenfolge fort.
Um dieses Problem zu beheben, empfiehlt es sich, für jeden Thread einen separaten Kontext zu haben, sodass Folgendes gilt:
- Sie sollten eine Kopie des Gerätekontexts erstellen, damit veränderliche Ressourcen (d. h. Ressourcen, die während der Anzeige oder beim Drucken variieren können, z. B. Textinhalte oder der Volltonfarbpinsel im Beispiel) beim Rendern nicht geändert werden. In diesem Beispiel sollten Sie eine Kopie dieser beiden Textzeilen und den Farbpinsel beibehalten, bevor Sie zeichnen. Auf diese Weise garantieren Sie, dass jeder Thread über vollständigen und konsistenten Inhalt verfügt, der gezeichnet und dargestellt werden kann.
- Sie sollten umfangreiche Ressourcen (z. B. Bitmaps und komplexe Effektdiagramme) gemeinsam nutzen, die einmal initialisiert und dann nicht threadübergreifend geändert werden, um die Leistung zu steigern.
- Sie können entweder einfachen Ressourcen (z. B. Volltonfarbenpinsel und Textformate) freigeben, die einmal initialisiert und dann nicht threadübergreifend geändert werden.
Zusammenfassung
Wenn Sie Multithread-Direct2D-Apps entwickeln, müssen Sie eine Multithread-Direct2D-Factory erstellen und dann alle Direct2D-Ressourcen von dieser Factory ableiten. Wenn ein Thread Direct3D- oder DXGI-Aufrufe aufruft, müssen Sie auch explizit die Direct2D-Sperre anwenden, um diese Direct3D- oder DXGI-Aufrufe zu schützen. Darüber hinaus müssen Sie die Kontextintegrität sicherstellen, indem Sie eine Kopie von veränderlichen Ressourcen für jeden Thread erstellen.