多執行緒 Direct2D 應用程式
如果您開發 Direct2D 應用程式,您可能需要從多個執行緒存取 Direct2D 資源。 在其他情況下,您可能想要使用多執行緒來取得更佳的效能或更好的回應性 (,例如使用一個執行緒來顯示幕幕,並使用個別執行緒進行離線轉譯) 。
本主題描述開發不含Direct3D轉譯的多執行緒Direct2D應用程式的最佳作法。 並行問題所造成的軟體瑕疵可能難以追蹤,而且規劃多執行緒原則並遵循此處所述的最佳做法很有説明。
注意
如果您存取兩個從兩個不同的單一線程 Direct2D 處理站建立的 Direct2D 資源,則只要基礎 Direct3D 裝置和裝置內容也不同,就不會產生存取衝突。 在本文中討論「存取 Direct2D 資源」時,實際上表示「除非另有說明」,否則「存取從相同 Direct2D 裝置建立的 Direct2D 資源」。
開發僅呼叫 Direct2D API 的Thread-Safe應用程式
您可以建立多執行緒 Direct2D 處理站實例。 您可以使用和共用多執行緒處理站及其來自多個執行緒的所有資源,但透過 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 呼叫間接存取資源,而 T2 直接透過 Direct3D 或 DXGI 呼叫存取相同的資源。
注意
Direct2D提供的執行緒保護 (此影像中的藍色鎖定) 在此案例中沒有説明。
若要避免這裡的資源存取衝突,建議您明確取得 Direct2D 用於內部存取同步處理的鎖定,並線上程需要進行 Direct3D 或 DXGI 呼叫時套用該鎖定,這可能會造成存取衝突,如下所示。 特別是,您應該特別小心使用以 HRESULT 傳回碼為基礎的例外狀況或早期系統的程式碼。 基於這個理由,建議您使用 RAII (資源取得是初始化) 模式來呼叫 Enter 和 Leave 方法。
這裡的程式碼示範何時要鎖定和解除鎖定 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 大於」,第 2 行是「比 B」,這兩者都會使用純黑色筆刷繪製。
- 執行緒 1 會繪製第一行文字。
- 執行緒 0 會分別回應使用者輸入、將文字行更新為 「B 小於」 和 「than A」,並針對自己的繪圖將筆刷色彩變更為純紅色;
- 執行緒 1 會繼續繪製第二行文字,現在為 「than A」,並加上紅色筆刷;
- 最後,我們會在螢幕外繪圖目標上取得兩行文字:「A 大於」,並以紅色表示「大於 A」。
在頂端資料列中,Thread 0 會使用目前的文字字串和目前的黑色筆刷繪製。 執行緒 1 只會在上半形完成螢幕外繪圖。
在中間資料列中,Thread 0 會回應使用者互動、更新文字字串和筆刷,然後重新整理畫面。 此時會封鎖執行緒 1。 在底端資料列中,執行緒 1 之後的最後一個螢幕外轉譯會繼續繪製底部半部,其中包含已改變筆刷和已改變的文字字串。
若要解決此問題,建議您為每個執行緒提供個別的內容,以便:
- 您應該建立裝置內容的複本,讓可變動的資源 (亦即,在顯示或列印期間可能會有所不同的資源,例如文字內容或範例中的純色筆刷,) 轉譯時不會變更。 在此範例中,您應該在繪製之前,維護這兩行文字和色彩筆刷的複本。 如此一來,您可以保證每個執行緒都有完整的一致內容來繪製和呈現。
- 您應該共用大量資源 (,例如點陣圖和複雜效果圖形,) 初始化一次,然後永遠不會線上程之間修改,以提高效能。
- 您可以共用輕量資源 (,例如純色筆刷和文字格式,) 初始化一次,然後永遠不會跨執行緒修改
摘要
當您開發多執行緒 Direct2D 應用程式時,您必須建立多執行緒 Direct2D 處理站,然後從該處理站衍生所有 Direct2D 資源。 如果執行緒會進行 Direct3D 或 DXGI 呼叫,您也必須明確取得 ,然後套用 Direct2D 鎖定來保護這些 Direct3D 或 DXGI 呼叫。 此外,您必須為每個執行緒提供可變動資源的複本,以確保內容完整性。