Share via


Direct2D-Multithread-Apps

Wenn Sie Direct2D-Apps entwickeln, müssen Sie möglicherweise über mehrere Threads auf Direct2D-Ressourcen zugreifen. In anderen Fällen können Sie Multithreading verwenden, um eine bessere Leistung oder eine bessere 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 Direct2D-Multithread-Apps mit wenig bis keinem Direct3D-Rendering beschrieben. Softwarefehler, die durch Parallelitätsprobleme verursacht werden, können schwierig aufzuspüren sein, 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 Direct2D-Einzelthread-Fabriken erstellt wurden, verursacht dies keine Zugriffskonflikte, solange die zugrunde liegenden Direct3D-Geräte und Gerätekontexte ebenfalls unterschiedlich sind. Wenn in diesem Artikel vom "Zugriff auf Direct2D-Ressourcen" die Rede ist, bedeutet dies wirklich "Zugriff auf Direct2D-Ressourcen, die von demselben Direct2D-Gerät erstellt wurden", sofern nicht anders angegeben.

Entwickeln Thread-Safe Apps, die nur Direct2D-APIs aufrufen

Sie können eine Multithread-Direct2D-Factory-instance erstellen. Sie können eine Multithread-Factory und alle zugehörigen Ressourcen aus mehr als einem Thread verwenden und freigeben, aber Zugriffe auf diese Ressourcen (über Direct2D-Aufrufe) werden von Direct2D serialisiert, sodass keine Zugriffskonflikte auftreten. Wenn Ihre App nur Direct2D-APIs aufruft, erfolgt dieser Schutz automatisch von Direct2D in einer granularen Ebene mit minimalem Mehraufwand. Der Code zum Erstellen einer Multithread-Factory hier.

ID2D1Factory* m_D2DFactory;

// Create a Direct2D factory.
HRESULT hr = D2D1CreateFactory(
    D2D1_FACTORY_TYPE_MULTI_THREADED,
    &m_D2DFactory
);

Die abbildung zeigt, wie Direct2D zwei Threads serialisiert, die Aufrufe nur mithilfe der Direct2D-API tätigen.

Diagramm mit zwei serialisierten Threads.

Entwickeln Thread-Safe Direct2D-Apps mit minimalen Direct3D- oder DXGI-Aufrufen

Es kommt mehr als häufig vor, dass eine Direct2D-App auch einige Direct3D - oder DXGI-Aufrufe durchführt. Beispielsweise wird ein Anzeigethread in Direct2D gezeichnet und dann mithilfe einer DXGI-Swapchain dargestellt.

In diesem Fall ist die Sicherung der Threadsicherheit komplizierter: Einige Direct2D-Aufrufe greifen indirekt auf die zugrunde liegenden Direct3D-Ressourcen zu, auf die gleichzeitig ein anderer Thread zugreift, der Direct3D oder DXGI aufruft. Da diese Direct3D- oder DXGI-Aufrufe nicht im Bewusstsein und der Kontrolle von Direct2D liegen, müssen Sie eine Multithread-Direct2D-Factory erstellen, aber sie müssen etwas tun, um Zugriffskonflikte zu vermeiden.

Das folgende Diagramm zeigt einen Direct3D-Ressourcenzugriffskonflikt aufgrund des indirekten Zugriffs von T0 auf eine Ressource über einen Direct2D-Aufruf und T2 auf die gleiche Ressource direkt über einen Direct3D- oder DXGI-Aufruf.

Hinweis

Der Threadschutz, den Direct2D bietet (die blaue Sperre in diesem Bild), hilft in diesem Fall nicht.

 

Threadschutzdiagramm.

Um hier Ressourcenzugriffskonflikte zu vermeiden, wird empfohlen, explizit die Sperre zu erwerben, die Direct2D für die interne Zugriffssynchronisierung verwendet, und diese Sperre anzuwenden, wenn ein Thread Direct3D - oder DXGI-Aufrufe ausführen muss, die einen Zugriffskonflikt verursachen können, wie hier gezeigt. Insbesondere sollten Sie mit Code, der Ausnahmen verwendet, oder ein Early Out-System auf Der Grundlage von HRESULT-Rückgabecodes besonders vorsichtig sein. 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 mit den Methoden Enter und Leave koppeln, andernfalls kann Ihre App deadlocken.

 

Der Code hier zeigt ein Beispiel für das Sperren und anschließende Entsperren von Direct3D - oder DXGI-Aufrufen.

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. Sie sollten sich dessen bewusst sein und sicherstellen, dass ein solches Verhalten keine Deadlocks verursacht. Weitere Informationen finden Sie im Thema DXGI-Übersicht .

 

Direct2d- und Direct3d-Threadsperrdiagramm.

Wenn Sie die Enter - und Leave-Methoden verwenden, werden die Aufrufe durch die automatische Direct2D - und die explizite Sperre geschützt, sodass die App keinen Zugriffskonflikt verursacht.

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 diese in der Regel eine bessere Leistung bietet, da die Parallelität auf einer viel feineren Ebene und mit geringerem Mehraufwand unter der Direct2D-Abdeckung geschützt wird.

Sicherstellen der Atomität zustandsbehafteter Vorgänge

Während Threadsicherheitsfeatures von DirectX sicherstellen können, dass keine zwei einzelnen API-Aufrufe gleichzeitig ausgeführt werden, müssen Sie auch sicherstellen, dass Threads, die zustandsbehaftete API-Aufrufe tätigen, sich nicht gegenseitig stören. Beispiel:

  1. Es gibt zwei Textzeilen, die Sie sowohl auf dem Bildschirm (nach 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 schwarzen Pinsel gezeichnet werden.
  2. Thread 1 zeichnet die erste Textzeile.
  3. Thread 0 reagiert auf eine Benutzereingabe, aktualisiert beide Textzeilen in "B ist kleiner" bzw. "als A" und änderte die Pinselfarbe für die eigene Zeichnung in einfarbiges Rot.
  4. Thread 1 setzt das Zeichnen der zweiten Textzeile fort, die jetzt "als A" ist, mit dem roten Farbpinsel;
  5. Schließlich erhalten wir zwei Textzeilen auf dem Off-Screen-Zeichenziel: "A ist größer" in Schwarz und "als A" in Rot.

Ein Diagramm der Threads auf dem und außerhalb des Bildschirms.

In der oberen Zeile zeichnet Thread 0 mit aktuellen Textzeichenfolgen und dem aktuellen schwarzen Pinsel. Thread 1 schließt nur die Off-Screen-Zeichnung in der oberen Hälfte ab.

In der mittleren Zeile reagiert Thread 0 auf Benutzerinteraktionen, aktualisiert die Textzeichenfolgen und den Pinsel und aktualisiert dann den Bildschirm. An diesem Punkt wird Thread 1 blockiert. In der unteren Zeile wird das letzte Offscreenrendering nach Thread 1 fortgesetzt, um die untere Hälfte mit einem geänderten Pinsel und einer geänderten Textzeichenfolge zu zeichnen.

Um dieses Problem zu beheben, empfiehlt es sich, einen separaten Kontext für jeden Thread zu verwenden, sodass:

  • Sie sollten eine Kopie des Gerätekontexts erstellen, damit sich veränderliche Ressourcen (d. h. Ressourcen, die während der Anzeige oder beim Drucken variieren können, z. B. Textinhalt oder der Farbtonpinsel im Beispiel) beim Rendern nicht ändern. In diesem Beispiel sollten Sie eine Kopie dieser beiden Textzeilen und den Farbpinsel beibehalten, bevor Sie zeichnen. Auf diese Weise stellen Sie sicher, dass jeder Thread vollständige und konsistente Inhalte aufweist, die gezeichnet und präsentiert werden müssen.
  • Sie sollten ressourcenintensive Ressourcen (z. B. Bitmaps und komplexe Effektdiagramme) gemeinsam nutzen, die einmal initialisiert und dann nie über Threads hinweg geändert werden, um die Leistung zu steigern.
  • Sie können entweder leichte Ressourcen (z. B. Farbpinsel und Textformate) gemeinsam nutzen, die einmal initialisiert und dann nie threadübergreifend geändert werden oder nicht.

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 durchführt, müssen Sie auch explizit abrufen und dann 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 der veränderlichen Ressourcen für jeden Thread haben.