多線程 Direct2D 應用程式
如果您開發 Direct2D 應用程式,您可能需要從多個線程存取 Direct2D 資源。 在其他情況下,您可能想要使用多線程來取得更好的效能或更好的回應性(例如使用一個線程進行屏幕顯示,另一個線程用於離線轉譯)。
本主題描述開發多線程 Direct2D 應用程式的最佳作法,幾乎沒有 Direct3D 轉譯。 並行問題所造成的軟體缺陷可能難以追蹤,而且規劃多線程原則並遵循此處所述的最佳做法很有説明。
注意
如果您存取兩個 Direct2D 從兩個不同的單個線程 Direct2D 處理站建立的資源,只要基礎 Direct3D 裝置和裝置內容也不同,它就不會造成存取衝突。 在談到本文中的「存取 Direct2D 資源」時,這確實表示「除非另有說明」,否則「存取從相同 Direct2D 裝置建立的 Direct2D 資源」。
開發僅呼叫 Direct2D API 的 Thread-Safe 應用程式
您可以建立多線程 Direct2D Factory 實例。 您可以使用並共用多線程處理站及其來自多個線程的所有資源,但透過 Direct2D 呼叫存取這些資源會由 Direct2D 串行化,因此不會發生存取衝突。 如果您的應用程式只呼叫 Direct2D API,則 Direct2D 會自動以最小額外負荷的細微層級來完成這類保護。 在這裡建立多線程處理站的程序代碼。
ID2D1Factory* m_D2DFactory;
// Create a Direct2D factory.
HRESULT hr = D2D1CreateFactory(
D2D1_FACTORY_TYPE_MULTI_THREADED,
&m_D2DFactory
);
此圖顯示 Direct2D 如何串行化兩個只使用 Direct2D API 進行呼叫的線程。
使用最少的 Direct3D 或 DXGI 呼叫開發 Thread-Safe Direct2D 應用程式
Direct2D 應用程式也經常進行一些 Direct3D 或 DXGI 呼叫。 例如,顯示線程會在 Direct2D 中繪製,然後使用 DXGI 交換鏈結呈現。
在此情況下,確保線程安全性更為複雜:某些 Direct2D 會間接存取基礎 Direct3D 資源,而另一個呼叫 Direct3D 或 DXGI 的線程可能會同時存取。 由於這些 Direct3D 或 DXGI 呼叫已脫離 Direct2D 的感知和控制,因此您必須建立多線程 Direct2D 處理站,但您必須執行 mor 以避免存取衝突。
下圖顯示 Direct3D 資源存取衝突,因為線程 T0 透過 Direct2D 呼叫間接存取資源,以及透過 Direct3D 或 DXGI 呼叫直接存取相同資源的 T2。
注意
Direct2D 提供的線程保護(此影像中的藍色鎖定)在此案例中無濟於事。
若要避免這裡的資源存取衝突,建議您明確取得 Direct2D 用於內部存取同步處理的鎖定,並在線程需要進行 Direct3D 或 DXGI 呼叫時套用該鎖定,如這裡所示。 特別是,您應該特別小心使用以 HRESULT 傳回碼為基礎的例外狀況或早出系統的程式代碼。 基於這個理由,我們建議您使用RAII(資源擷取為初始化)模式來 呼叫 enter,並 保留 方法。
這裡的程式代碼顯示何時要鎖定,然後在 direct3D 或 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();
}
注意
某些 Direct3D 或 DXGI 呼叫(特別是 IDXGISwapChain::P resent)可能會取得呼叫函式或方法程式代碼的鎖定和/或觸發回呼。 您應該注意這一點,並確定這類行為不會造成死結。 如需詳細資訊,請參閱 DXGI 概觀 主題。
當您使用 Enter 和 Leave 方法時,呼叫會受到自動 Direct2D 和明確鎖定的保護,因此應用程式不會發生存取衝突。
有其他方法可以解決此問題。 不過,建議您使用 Direct2D 鎖定來明確 保護 Direct3D 或 DXGI 呼叫,因為它通常會提供更佳的效能,因為它在更精細的層級保護並行,而且在 Direct2D 的涵蓋下額外負荷較低。
確保具狀態作業的不可部分完成性
雖然 DirectX 的線程安全性功能可協助確保不會同時進行兩個個別的 API 呼叫,但您也必須確保進行具狀態 API 呼叫的線程不會互相干擾。 以下是範例。
- 有兩行文字您想要同時轉譯到螢幕上(由線程 0)和螢幕外(由線程 1):第 1 行是 “A 更大”,而 Line #2 是 “than B”,這兩行都會使用純黑色筆刷繪製。
- 線程 1 會繪製第一行文字。
- 線程 0 會分別對使用者輸入做出反應、將文字行更新為 “B 較小”和 “than A”,並將筆刷色彩變更為純紅色,以供本身繪製使用:
- 線程 1 會繼續繪製第二行文字,也就是現在為 “than A”,並加上紅色筆刷:
- 最後,我們會在螢幕外繪圖目標上取得兩行文字:黑色“A 更大”,紅色為 “than A”。
在頂端數據列中,Thread 0 會使用目前的文字字串和目前的黑色筆刷繪製。 線程 1 只會在上半場完成螢幕外繪圖。
在中間數據列中,Thread 0 會回應用戶互動、更新文字字串和筆刷,然後重新整理畫面。 此時,線程 1 會遭到封鎖。 在底部數據列中,Thread 1 之後的最後一個螢幕外轉譯會繼續使用已改變的筆刷和改變的文字字串繪製下半半部分。
若要解決此問題,建議您為每個線程提供個別的內容,以便:
- 您應該建立裝置內容的複本,讓可變的資源(也就是在顯示或列印期間可能會改變的資源,例如文字內容或範例中的純色筆刷)不會在您轉譯時變更。 在此範例中,您應該保留這兩行文字和色彩筆刷的複本,再繪製。 如此一來,您可以保證每個線程都有完整且一致的內容來繪製和呈現。
- 您應該共用大量資源(例如位圖和複雜效果圖),這些資源會初始化一次,然後從未在線程之間修改,以提升效能。
- 您可以共用輕量型資源(例如純色筆刷和文字格式),這些資源會初始化一次,然後永遠不會在線程之間修改
總結
當您開發多線程 Direct2D 應用程式時,您必須建立多線程 Direct2D 處理站,然後從該處理站衍生所有 Direct2D 資源。 如果線程會 Direct3D 或 DXGI 呼叫,您也必須明確取得 ,然後套用 Direct2D 鎖定來保護這些 Direct3D 或 DXGI 呼叫。 此外,您必須為每個線程擁有可變動資源的複本,以確保內容完整性。