App Direct2D multithreading
Se si sviluppano app Direct2D, potrebbe essere necessario accedere alle risorse Direct2D da più thread. In altri casi, è possibile usare il multi-threading per ottenere prestazioni migliori o maggiore velocità di risposta, ad esempio usando un thread per la visualizzazione dello schermo e un thread separato per il rendering offline.
In questo argomento vengono descritte le procedure consigliate per lo sviluppo di app Direct2D multithreaded con poco rendering Direct3D . I difetti software causati da problemi di concorrenza possono essere difficili da tenere traccia e è utile pianificare i criteri di multithreading e seguire le procedure consigliate descritte qui.
Nota
Se si accede a due risorse Direct2D create da due diverse factory Direct2D con thread singolo, non causa conflitti di accesso, purché i dispositivi Direct3D sottostanti e i contesti del dispositivo siano distinti. Quando si parla di "accesso alle risorse Direct2D" in questo articolo, significa in realtà "accedere alle risorse Direct2D create dallo stesso dispositivo Direct2D" a meno che non sia indicato diversamente.
Sviluppo di app Thread-Safe che chiamano solo API Direct2D
È possibile creare un'istanza di factory Direct2D multithreaded. È possibile usare e condividere una factory multithreaded e tutte le relative risorse da più thread, ma gli accessi a tali risorse (tramite chiamate Direct2D) vengono serializzati da Direct2D, quindi non si verificano conflitti di accesso. Se l'app chiama solo API Direct2D, tale protezione viene eseguita automaticamente da Direct2D in un livello granulare con sovraccarico minimo. Codice per creare una factory multithreaded qui.
ID2D1Factory* m_D2DFactory;
// Create a Direct2D factory.
HRESULT hr = D2D1CreateFactory(
D2D1_FACTORY_TYPE_MULTI_THREADED,
&m_D2DFactory
);
L'immagine mostra come Direct2D serializza due thread che emettono chiamate usando solo l'API Direct2D.
Sviluppo di app direct2D Thread-Safe con chiamate Direct3D o DXGI minime
È più spesso che un'app Direct2D effettua anche alcune chiamate Direct3D o DXGI. Ad esempio, un thread visualizzato disegna in Direct2D e quindi presenta una catena di scambio DXGI.
In questo caso, garantire la sicurezza del thread è più complicata: alcune chiamate Direct2D accedono indirettamente alle risorse Direct3D sottostanti, che potrebbero essere accessibili simultaneamente da un altro thread che chiama Direct3D o DXGI. Poiché queste chiamate Direct3D o DXGI non sono consapevoli e controllate da Direct2D, è necessario creare una factory Direct2D multithreaded, ma è necessario eseguire mor per evitare conflitti di accesso.
Il diagramma seguente mostra un conflitto di accesso alle risorse Direct3D dovuto all'accesso a una risorsa indirettamente tramite una chiamata Direct2D e T2 che accede direttamente alla stessa risorsa tramite una chiamata Direct3D o DXGI.
Nota
La protezione del thread fornita da Direct2D (blocco blu in questa immagine) non aiuta in questo caso.
Per evitare conflitti di accesso alle risorse, è consigliabile acquisire in modo esplicito il blocco usato da Direct2D per la sincronizzazione di accesso interno e applicare tale blocco quando un thread deve effettuare chiamate Direct3D o DXGI che potrebbero causare conflitti di accesso, come illustrato qui. In particolare, è consigliabile prestare particolare attenzione al codice che usa eccezioni o un sistema di uscita anticipata in base ai codici restituiti HRESULT. Per questo motivo, è consigliabile usare un modello RAII (Resource Acquisition Is Initialization) per chiamare i metodi Enter e Leave .
Nota
È importante associare chiamate ai metodi Invio e Lasciare , in caso contrario, l'app può eseguire il deadlock.
Il codice seguente mostra un esempio di quando bloccare e quindi sbloccare le chiamate Direct3D o DXGI.
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();
}
Nota
Alcune chiamate Direct3D o DXGI (in particolare IDXGISwapChain::P resent) possono acquisire blocchi e/o trigger callback nel codice della funzione o del metodo chiamante. Si dovrebbe essere consapevoli di questo e assicurarsi che tale comportamento non causa deadlock. Per altre informazioni, vedere l'argomento Panoramica di DXGI .
Quando si usano i metodi Invio e Leave , le chiamate sono protette dal direct2D automatico e dal blocco esplicito, in modo che l'app non colpisca il conflitto di accesso.
Esistono altri approcci per risolvere questo problema. Tuttavia, è consigliabile proteggere in modo esplicito le chiamate Direct3D o DXGI con il blocco Direct2D perché in genere offre prestazioni migliori perché protegge la concorrenza a un livello molto più fine e con un sovraccarico inferiore sotto la copertura di Direct2D .
Garantire l'atomicità delle operazioni con stato
Anche se le funzionalità di sicurezza del thread di DirectX possono garantire che non vengano eseguite due singole chiamate API simultaneamente, è necessario assicurarsi anche che i thread che effettuano chiamate API con stato non interferiscano tra loro. Ecco un esempio.
- Esistono due righe di testo che si desidera eseguire il rendering sia sullo schermo (da Thread 0) che sullo schermo (in base al thread 1): la riga #1 è "A è maggiore" e Line #2 è "di B", entrambe le quali verranno disegnate usando un pennello nero solido.
- Thread 1 disegna la prima riga di testo.
- Il thread 0 reagisce a un input utente, aggiorna entrambe le righe di testo a "B è più piccolo" e "di A" rispettivamente e ha modificato il colore del pennello in rosso a tinta unita per il proprio disegno;
- Thread 1 continua a disegnare la seconda riga di testo, che è ora "than A", con il pennello di colore rosso;
- Infine, si ottengono due righe di testo nella destinazione di disegno fuori schermo: "A è maggiore" in nero e "di A" in rosso.
Nella riga superiore, Thread 0 disegna con stringhe di testo correnti e il pennello nero corrente. Il thread 1 termina solo il disegno fuori schermo nella metà superiore.
Nella riga centrale Thread 0 risponde all'interazione utente, aggiorna le stringhe di testo e il pennello, quindi aggiorna la schermata. A questo punto, Thread 1 viene bloccato. Nella riga inferiore, il rendering finale fuori schermo dopo Thread 1 riprende a disegnare la metà inferiore con un pennello modificato e una stringa di testo modificata.
Per risolvere questo problema, è consigliabile disporre di un contesto separato per ogni thread, in modo che:
- È consigliabile creare una copia del contesto del dispositivo in modo che le risorse modificabili ,ad esempio le risorse che possono variare durante la visualizzazione o la stampa, ad esempio il contenuto del testo o il pennello a tinta unita nell'esempio, non cambiano quando si esegue il rendering. In questo esempio è necessario mantenere una copia di queste due righe di testo e il pennello di colore prima di disegnare. A tale scopo, si garantisce che ogni thread abbia contenuto completo e coerente per disegnare e presentare.
- È consigliabile condividere risorse di peso elevato ,ad esempio bitmap e grafici di effetti complessi, inizializzati una volta e poi mai modificati tra thread per aumentare le prestazioni.
- È possibile condividere risorse di peso leggero (ad esempio pennelli di colore a tinta unita e formati di testo) inizializzati una volta e quindi mai modificati tra thread o meno
Riepilogo
Quando si sviluppano app Direct2D multithreaded, è necessario creare una factory Direct2D multithreaded e quindi derivare tutte le risorse Direct2D da tale factory. Se un thread eseguirà chiamate Direct3D o DXGI, è anche necessario acquisire in modo esplicito il blocco Direct2D per proteggere le chiamate Direct3D o DXGI. È inoltre necessario garantire l'integrità del contesto avendo una copia di risorse modificabili per ogni thread.