Teilen über


Multithreaded Direct2D-Apps

Wenn Sie Direct2D--Apps entwickeln, müssen Sie möglicherweise über mehrere Threads auf Direct2D-Ressourcen zugreifen. In anderen Fällen möchten Sie möglicherweise Multithreading verwenden, um eine bessere Leistung oder eine bessere Reaktionsfähigkeit zu erzielen (z. B. die Verwendung eines Threads für die Bildschirmanzeige und einen separaten Thread für das Offlinerendering).

In diesem Thema werden die bewährten Methoden für die Entwicklung von Multithread-Direct2D--Apps ohne Direct3D- Rendering beschrieben. Softwarefehler, die durch Parallelitätsprobleme verursacht werden, können schwierig auffindbar sein, und es ist hilfreich, Ihre Multithreading-Richtlinie zu planen und die hier beschriebenen bewährten Methoden zu befolgen.

Anmerkung

Wenn Sie auf zwei Direct2D- Ressourcen zugreifen, die aus zwei verschiedenen Direct2D-Fabriken erstellt wurden, führt dies nicht zu Zugriffskonflikten, solange die zugrunde liegenden Direct3D- Geräte und Gerätekontexte ebenfalls unterschiedlich sind. Wenn sie in diesem Artikel über den "Zugriff auf Direct2D-Ressourcen" sprechen, bedeutet dies wirklich "Zugreifen auf Direct2D-Ressourcen, die auf demselben Direct2D-Gerät erstellt wurden", sofern nicht anders angegeben.

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

Sie können eine Multithread-Direct2D- Factoryinstanz erstellen. Sie können eine Multithread-Factory und alle zugehörigen Ressourcen aus mehr als einem Thread verwenden und freigeben, aber Der Zugriff auf diese Ressourcen (über Direct2D-Aufrufe) wird von Direct2D serialisiert, sodass keine Zugriffskonflikte auftreten. Wenn Ihre App nur Direct2D-APIs aufruft, erfolgt dieser Schutz automatisch von Direct2D auf einer granularen Ebene mit minimalem Aufwand. 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 folgende Abbildung zeigt, wie Direct2D zwei Threads serialisiert, die Nur die Direct2D-API verwenden.

Diagramm von zwei serialisierten Threads.

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

Es ist häufiger, dass eine Direct2D--App auch einige Direct3D- oder DXGI-Aufrufe vorgibt. Beispielsweise wird in Direct2D ein Anzeigethread mit einer DXGI-Swapchaindargestellt.

In diesem Fall ist die Sicherstellung der Threadsicherheit komplizierter: Einige Direct2D- Aufrufe greifen indirekt auf die zugrunde liegenden Direct3D--Ressourcen zu, auf die gleichzeitig von einem anderen Thread zugegriffen werden kann, der Direct3D oder DXGI aufruft. Da diese Direct3D- oder DXGI-Aufrufe außerhalb des Bewusstseins und der Kontrolle von Direct2D liegen, müssen Sie eine Multithread-Direct2D-Factory erstellen, aber Sie müssen mors tun, um Zugriffskonflikte zu vermeiden.

Das folgende Diagramm zeigt einen Direct3D- Ressourcenzugriffskonflikt aufgrund von Thread T0, der indirekt über einen Direct2D-Aufruf Direct2D und T2 auf dieselbe Ressource direkt über einen Direct3D- oder DXGI-Aufruf zugreift.

Anmerkung

Der Threadschutz, der Direct2D- bereitstellt (die blaue Sperre in diesem Bild), hilft in diesem Fall nicht.

 

Threadschutzdiagramm.

Um einen Ressourcenzugriffskonflikt hier zu vermeiden, empfehlen wir Ihnen, die Sperre explizit abzurufen, die Direct2D- für die interne Zugriffssynchronisierung verwendet, und wenden Sie diese Sperre an, wenn ein Thread Direct3D-- oder DXGI-Aufrufe durchführen muss, die einen Zugriffskonflikt verursachen können, wie hier gezeigt. Insbesondere sollten Sie mit Code, der Ausnahmen oder ein Early Out-System verwendet, auf der Grundlage von HRESULT-Rückgabecodes besondere Sorgfalt beachten. Aus diesem Grund wird empfohlen, ein RAII-Muster (Resource Acquisition Is Initialization) zu verwenden, um die Enter- aufzurufen und Leave-Methoden aufzurufen.

Anmerkung

Es ist wichtig, dass Sie Aufrufe der Enter- und Verlassen Methoden verbinden, andernfalls kann Ihre App eine Sperre durchführen.

 

Der folgende Code zeigt ein Beispiel für das Sperren und 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();
}

Anmerkung

Einige Direct3D-- oder DXGI-Aufrufe (insbesondere IDXGISwapChain::P resent) können Sperren abrufen und/oder Rückrufe in den Code der aufrufenden Funktion oder Methode auslösen. Beachten Sie dies, und stellen Sie sicher, dass dieses Verhalten keine Deadlocks verursacht. Weitere Informationen finden Sie im Thema DXGI Overview.

 

Direct2d- und Direct3D-Threadsperrdiagramm.

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

Es gibt andere Ansätze, um dieses Problem zu umgehen. Es wird jedoch empfohlen, Direct3D-- oder DXGI-Aufrufe mit der Direct2D--Sperre explizit zu schützen, da sie in der Regel eine bessere Leistung bietet, da sie die Parallelität auf einer viel feineren Ebene und mit geringerem Aufwand unter direct2D-Abdeckung schützt.

Sicherstellung der Atomität staatlicher Operationen

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 ausführen, nicht miteinander stören. Hier ist ein Beispiel.

  1. Es gibt zwei Textzeilen, die Sie sowohl auf dem Bildschirm (nach Thread 0) als auch auf dem Offbildschirm (nach Thread 1) rendern möchten: Zeile #1 ist "A ist größer" und Zeile #2 ist "als B", die beide mit einem einfarbigen schwarzen Pinsel gezeichnet werden.
  2. Thread 1 zeichnet die erste Textzeile.
  3. Thread 0 reagiert auf eine Benutzereingabe, aktualisiert beide Textzeilen auf "B ist kleiner" bzw. "als A", und änderte die Pinselfarbe für die eigene Zeichnung in Volltonrot;
  4. Thread 1 zeichnet weiterhin die zweite Textzeile, die jetzt "als A" ist, mit dem roten Farbpinsel;
  5. Schließlich erhalten wir zwei Textzeilen auf dem Offscreen-Zeichnungsziel: "A ist größer" in Schwarz und "als A" in Rot.

ein Diagramm von Ein- und Aus-Bildschirmthreads.

In der obersten Zeile zeichnet Thread 0 mit aktuellen Textzeichenfolgen und dem aktuellen schwarzen Pinsel. Thread 1 beendet nur die Offscreen-Zeichnung auf der oberen Hälfte.

In der mittleren Zeile antwortet 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 endgültige Offscreen-Rendering nach dem Fortsetzen des Zeichnens der unteren Hälfte mit einem geänderten Pinsel und einer geänderten Textzeichenfolge fortgesetzt.

Um dieses Problem zu beheben, empfehlen wir, dass Sie für jeden Thread einen separaten Kontext haben, sodass:

  • Sie sollten eine Kopie des Gerätekontexts erstellen, damit sich veränderbare Ressourcen (d. h. Ressourcen, die beim Anzeigen oder Drucken variieren können, z. B. Textinhalte oder der Pinsel für Volltonfarbe im Beispiel), beim Rendern nicht ändern. In diesem Beispiel sollten Sie eine Kopie dieser beiden Textzeilen und den Farbpinsel beibehalten, bevor Sie zeichnen. Dadurch garantieren Sie, dass jeder Thread vollständige und konsistente Inhalte zum Zeichnen und Präsentieren hat.
  • Sie sollten Ressourcen mit hoher Gewichtung (z. B. Bitmaps und komplexe Effektdiagramme) teilen, die einmal initialisiert und dann nie über Threads geändert werden, um die Leistung zu erhöhen.
  • Sie können ressourcen mit geringem Gewicht (z. B. Pinsel und Textformate) teilen, die einmal initialisiert und dann nie über Threads hinweg 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 führt, 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änderbaren Ressourcen für jeden Thread verwenden.