Skip to main content
Microsoft Game Development Kit (GDK) 為非同步 API 實作了一種新模式, 以回應我們從遊戲開發人員收到、關於 XBOX One ERA 程式設計模型中所實作之非同步模式的意見反應。 我們的目標是讓這個新模式更容易整合到 一般的遊戲架構中,並為遊戲開發人員提供他們所要求的高度 控制權。本主題說明該設計 模式,並提出一個可用來 實作非同步模式的程式庫。

概念模型

Microsoft Game Development Kit (GDK) 中的非同步程式設計分為 2 個主要元件: 工作和工作佇列。雖然程式庫中還有更多功能, 但整個概念模型都使用這 2 個主要元件。 工作是一組單一的非同步作業,可以啟動、檢查其 狀態、可能被取消、完成,並傳回其 完成資訊。在 Microsoft Game Development Kit (GDK) 模型中,工作由兩個 主體組成:工作回呼和完成回呼。這可提供 更多控制權,例如完全平行處理,或將平行工作 與單一執行緒完成結合。 工作佇列是一個容器,會將工作回呼和完成 回呼加入佇列以供稍後執行。工作佇列中有兩個內部佇列, 稱為連接埠,分別處理工作回呼和完成回呼。 這兩者稱為工作連接埠和完成連接埠。 圖 1. 工作和工作佇列的圖表 工作和工作佇列的圖表。 工作佇列的每個連接埠都會在建立 時以不同方式設定,以產生不同的回呼執行行為。例如, 工作連接埠可設定為非同步,而 完成連接埠可設定為在主執行緒上序列執行。 可以設定手動設定,以完全控制執行 行為。連接埠設定模式會在下文說明。 啟動非同步工作時,回呼不會立即加入 工作佇列。非同步提供者 會處理狀態變更,以確保工作在完成回呼加入佇列並分派之前 先加入佇列並分派。 工作佇列本身不會直接處理執行緒。而是 依賴外部呼叫來分派其連接埠。 外部呼叫會決定執行緒和並行行為。 工作佇列本身是完全安全執行緒的。 圖 2. 在多個執行緒上分派連接埠 顯示在多個執行緒上分派連接埠的影像。 基本上就是這樣!工作的回呼會加入工作 佇列的工作連接埠和完成連接埠,而該工作佇列會以某種方式 分派這些回呼。API 包含一整套 功能,可用來管理工作佇列、檢查回呼狀態、追蹤 工作資料、建立自訂工作處理等。 Microsoft Game Development Kit (GDK) 非同步 API 呼叫一律會在內部實作工作回呼, 而完成回呼一律是選用的。對於 Microsoft Game Development Kit (GDK) 非同步 呼叫以外的用法,您必須提供工作回呼。

需求

遊戲開發人員列出了下列 API 呼叫需求。
  1. 偏好同步呼叫而非非同步呼叫
  2. 提供具有輪詢的非同步
  3. 提供具有回呼的非同步
  4. 提供對非同步工作在哪個執行緒上執行的控制權
  5. 提供對完成回呼在哪個執行緒上執行的 控制權

API 類型

Microsoft Game Development Kit (GDK) 致力於讓 API 設計非常直接明瞭。遊戲開發人員是微調程式碼以 充分利用硬體的專家。我們會盡可能 將控制權交給他們。API 實作可分為下列類型。
  • 時間敏感安全: 時間敏感安全 API 是可以 在時間敏感執行緒上呼叫的 API。請注意,雖然這通常 表示 API 很簡單或非常快速,但關鍵概念在於 API 的效能特性是一致的。它們 一律是同步的,永遠不需要非同步 版本。這些 API 應記載為時間敏感安全。
  • 非時間敏感安全: 這些 API 從 轉譯執行緒呼叫並不安全。其效能特性可能差異 很大。大多數 API 都屬於此類別。
  • 非同步: 這些 API 本質上是非同步的,例如 Web 服務呼叫。它們使用本主題中所述的 非同步模式。非同步 API 在 Microsoft Game Development Kit (GDK) 中不像在 XBOX One ERA 程式設計模型中那麼常見;非同步 API 通常會長時間執行且可取消。除了少數 特定使用案例之外,非同步 API 都會有 非時間關鍵安全的同步版本。呼叫非同步 API 應一律是時間關鍵安全的。
  • 通知: 通知本質上是週期性的,沒有 明確的結束。它們與非同步 API 相關,但由於 其週期性本質,對開發人員而言,它們的外觀和行為應 有所不同。註冊通知應一律是 時間關鍵安全的。

非同步 API 模式

Microsoft Game Development Kit (GDK) 引進了一種通用的非同步 API 模式,Microsoft Game Development Kit (GDK) 元件可以使用此模式來提供一致的非同步支援。其 核心是一個類似 OVERLAPPED 的結構,稱為 XAsyncBlock:
XAsyncBlock 是由呼叫端提供的結構。呼叫端會填入 此結構中的選用欄位,如下表所示。 Internal 欄位由系統使用,不應修改。 在非同步作業期間,不應修改此結構中 使用者可設定的欄位。XAsyncBlock 必須在非同步作業的 存留期間保留在記憶體中。如果 XAsyncBlock 是動態配置的, 最早可在完成回呼中將其刪除。 除了 XAsyncBlock 之外,還有少數幾個協助程式 API,如下所示。
XAsyncGetStatus 會傳回非同步呼叫的狀態。呼叫 開始時,此狀態為 E_PENDING。完成時,它會變更為 S_OK 或 特定錯誤。如果呼叫已取消,則會傳回 E_ABORT。 XAsyncGetResultSize 會傳回取得呼叫結果 所需的緩衝區大小。用來擷取結果的實際 API 會針對 每個非同步呼叫量身打造。 XAsyncCancel 可用來取消呼叫。取消與否取決於被取消的作業,可能會同步發生、非同步發生,或完全不發生。如果作業已取消, XAsyncGetResult、XAsyncGetResultSize 或 XAsyncGetStatus 會傳回 E_ABORT。已取消的 呼叫會向 XAsyncBlock 的 XAsyncCompletionRoutine 參數發出訊號,並叫用其 回呼。 XAsyncRun 是一個協助程式方法,可以非同步執行任何程式碼。

非同步 API 使用方式

首先,讓我們看看下列程式碼範例中的同步 API。
此 API 會呼叫 Web 服務,以判斷遊戲存檔儲存空間 還剩下多少。為了新增非同步支援,我們宣告一對新的 API。
如果非同步呼叫已 啟動,XGameSaveGetRemainingQuotaAsync 會傳回 S_OK (因為此 API 只有非同步版本,傳回 E_PENDING 沒有意義)。XGameSaveGetRemainingQuotaResult 在呼叫完成之前 會傳回 E_PENDING。 讓我們看看實際的運作方式,如下所示。
XAsyncBlocks 都需要工作佇列 (如下所述),工作佇列會控制 非同步呼叫的執行位置和方式。如果未提供,則會使用 整個處理序的工作佇列。 請注意,XAsyncBlock 必須在非同步呼叫的存留期間 保留在記憶體中。在此範例中,它是動態配置的,並在 完成回呼中刪除。它也可以儲存為全域或成員 變數。如果同時將相同的 XAsyncBlock 用於 多個非同步呼叫,會導致未定義的行為。 XGameSaveGetRemainingQuotaResult 會完成非同步呼叫的循環。 它會釋放非同步區塊中的內部資料,因此該區塊現在可以 用於新的呼叫。後續對 XGameSaveGetRemainingQuotaResult 的呼叫都會失敗。 XGameSaveGetRemainingQuotaAsync 和 XGameSaveGetRemainingQuotaResult 也會在非同步區塊內配對;如果您 將某個非同步呼叫與另一個結果 API 不當搭配,就會發生錯誤。 如果非同步呼叫沒有資料承載,也就是只有 HRESULT 狀態 重要,請定義只接受非同步區塊的 Result 方法,如下所示。

控制工作分派

在先前的呼叫中,是哪個執行緒執行了非同步工作?是哪個執行緒叫用了 完成回呼?這取決於指派給 XAsyncBlock 的工作佇列。 工作佇列有兩個「連接埠」:工作連接埠和完成連接埠。每個 連接埠都有一個分派模式,決定如何處理加入連接埠佇列的回呼。 分派模式有好幾種。
  • 執行緒集區: 加入執行緒集區佇列的回呼會 在系統執行緒集區上執行。執行緒集區會平行叫用 這些呼叫,在執行緒集區執行緒可用時 依序從佇列中取出呼叫來執行。
  • 序列化執行緒集區: 回呼會加入佇列並在 執行緒集區上執行,但一次只執行一個。
  • 手動: 加入手動佇列的回呼不會自動 分派。由開發人員決定要在 哪個執行緒上分派它們。
  • 立即: 立即分派模式完全不會加入佇列。它會 立即在提交回呼的執行緒上執行 該呼叫。
系統已設定一個預設的處理序工作佇列,因此工作連接埠 和完成連接埠都會透過系統執行緒集區分派。如果 XAsyncBlock 中未傳入佇列參數,就會使用此 處理序工作佇列。遊戲也可以停用處理序工作佇列,要求 必須將佇列傳入 XAsyncBlock。 我們預期許多開發人員會選擇手動分派模式,以 完全控制非同步工作和完成 回呼的執行時機和位置。 如需工作佇列的詳細資料,請參閱非同步工作佇列設計。

通知

通知可能沒有結束,而且可能會被呼叫許多次。 通知應支援非同步 呼叫需求的子集。
  1. 具有輪詢的非同步
  2. 具有回呼的非同步
  3. 控制回呼在哪個執行緒上發生
通知會使用工作佇列,讓開發人員能夠控制 回呼執行緒,但除此之外不會使用非同步區塊;它們 的設計更像具有 Register 和 Unregister 方法的標準事件。
  • Register 方法,會接受任何呼叫特定的參數、工作 佇列、選用的 void 內容,以及強型別的回呼 指標。最後一個參數是輸出參數,會 傳回權杖。
  • Unregister 方法,會接受任何呼叫特定的內容以及 權杖。
  • 藉由新增與通知回呼無關的個別方法來 支援輪詢。
讓我們看看下列可擷取 Windows 訊息的範例。
請注意,在此範例中,UnregisterMessageAvailable 會接受最後一個 “wait” 參數並傳回 bool。這可讓呼叫端決定在 呼叫正被叫用時如何處理取消註冊。

非同步程式庫

為了更容易建立支援非同步模式的一致 API, 我們提供了一個程式庫,可用來實作 API 的「非同步 管線」。此程式庫的 API 如下所示。
此 API 使用單一回呼,並結合一個作業值來 指出呼叫 API 的原因。另外還有一個會隨著呼叫進行 而填入的單一資料結構。若要使用此 API, 請執行下列動作。
  1. 使用呼叫端傳遞的非同步區塊呼叫 XAsyncBegin,並 提供一個提供實作的回呼。
  2. 執行呼叫的非同步工作。如果您需要在 背景工作執行緒上執行工作,請呼叫 XAsyncSchedule。如果您可以 使用 OS 非同步基本型別來執行工作,並能足夠快速地設定這些基本型別以 維持時間關鍵安全,則建議採用此方式。
  3. 如果您需要從背景工作執行緒回呼叫用其他非同步工作, 可以從背景工作傳回 E_PENDING。您也可以 從背景工作內部呼叫 XAsyncSchedule,以重新排程額外的 工作。
  4. 所有工作完成時,請呼叫 XAsyncComplete。
  5. 在 XAsyncGetResult 周圍提供強型別包裝函式,以傳回 結果。
  6. 如果您的非同步呼叫沒有資料承載,您應該在 XAsyncGetStatus 周圍提供強型別包裝函式,並將零作為所需 緩衝區大小傳遞給 XAsyncComplete。
非同步提供者回呼會以下列 作業叫用。
  • Begin 在 XAsyncBegin 期間,會以此 opcode 叫用非同步提供者。如果提供者實作此 op code,則應透過呼叫 XAsyncSchedule 或透過外部方式啟動其非同步工作。此回呼會在 XAsyncBegin 呼叫鏈中同步呼叫,因此絕不應封鎖。
  • DoWork 在已呼叫 XAsyncSchedule 以 使用工作佇列排程非同步工作的情況下呼叫。提供者函式會執行 它需要的任何工作。完成時,它會使用 結果碼和資料承載大小呼叫 XAsyncComplete;如果呼叫沒有 資料承載,大小可以為零。如果需要完成更多非同步工作, 提供者可以排程該工作,並應傳回 E_PENDING。
  • GetResult 呼叫以擷取呼叫的結果。由於 資料大小會在呼叫完成時傳入 XAsyncComplete,因此這裡 不需要檢查引數;所有緩衝區和緩衝區大小 都已由程式庫驗證。
  • Cancel 在使用者取消非同步呼叫時呼叫。如果呼叫可以取消,請將其取消,並以 E_ABORT 作為結果碼呼叫 XAsyncComplete。
  • Cleanup 在呼叫完全結束時呼叫,此時 提供者可以刪除任何動態記憶體。
非同步提供者只需要實作其所需的作業。例如, 沒有清除作業的不可取消非同步 IO 只需要 實作 GetResult。 以下是 FactorialAsync 方法的範例,此方法以非同步方式 實作階乘。

參考 API 文件

另請參閱

非同步程式設計設計目標與改進 非同步工作佇列設計
Last modified on October 6, 2026