> ## Documentation Index
> Fetch the complete documentation index at: https://devdocs.xbox.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 在 XSAPI C API 中进行异步调用

> 使用 XAsyncBlock 和 XTaskQueueHandle 控制 XBOX Live 标题中 XSAPI C 异步调用（如 XblProfileGetUserProfileAsync）的线程。

**异步 API** 是一种快速返回但启动**异步任务**的 API，其结果会在任务完成后返回。

传统上，游戏在使用**完成回调**时对于哪个线程执行**异步任务**以及哪个线程返回结果几乎没有控制权。有些游戏被设计为堆的某个部分只由单个线程访问，以避免需要线程同步。如果**完成回调**不是从游戏控制的线程中调用的，则用**异步任务**的结果更新共享状态将需要线程同步。

XSAPI C API 公开了一个新的异步 C API，该 API 使开发人员在进行**异步 API** 调用时可以直接控制线程，例如 **XblSocialGetSocialRelationshipsAsync()**、**XblProfileGetUserProfileAsync()** 和 **XblAchievementsGetAchievementsForTitleIdAsync()**。

以下是一个调用 **XblProfileGetUserProfileAsync** API 的基本示例：

```cpp theme={null}
 XAsyncBlock* asyncBlock = new XAsyncBlock();
    asyncBlock->queue = GlobalState()->queue;
    asyncBlock->context = nullptr;
    asyncBlock->callback = [](XAsyncBlock* asyncBlock)
    {
        XblUserProfile profile = { 0 };
        HRESULT hr = XblProfileGetUserProfileResult(asyncBlock, &profile);
        delete asyncBlock;
    };

    HRESULT hr = XblProfileGetUserProfileAsync(GlobalState()->xboxLiveContext, GlobalState()->xboxUserId, asyncBlock);
```

要理解此调用模式，你需要了解如何使用 **XAsyncBlock** 和 **XTaskQueueHandle**。

* **XAsyncBlock** 携带与**异步任务**和**完成回调**相关的所有信息。

* **XTaskQueueHandle** 允许你确定哪个线程执行**异步任务**以及哪个线程调用 XAsyncBlock 的**完成回调**。

## **XAsyncBlock**

让我们详细看看 **XAsyncBlock**。它是一个结构体，定义如下：

```cpp theme={null}
typedef struct XAsyncBlock
{
    /// <summary>
    /// The queue to queue the call on
    /// </summary>
    XTaskQueueHandle queue;

    /// <summary>
    /// Optional context pointer to pass to the callback
    /// </summary>
    void* context;

    /// <summary>
    /// Optional callback that will be invoked when the call completes
    /// </summary>
    XAsyncCompletionRoutine* callback;

    /// <summary>
    /// Internal use only
    /// </summary>
    unsigned char internal[sizeof(void*) * 4];
};
```

**XAsyncBlock** 包含：

* *queue* - 一个 XTaskQueueHandle，它是一个句柄，表示有关在何处运行工作的信息。如果未设置，则将使用默认队列。

* *context* - 允许你向回调函数传递数据。

* *callback* - 一个可选的回调函数，在异步工作完成后将被调用。如果你不指定回调，则可以使用 **XAsyncGetStatus** 等待 **XAsyncBlock** 完成，然后获取结果。

你应该为每个调用的异步 API 在堆上创建一个新的 XAsyncBlock。XAsyncBlock 必须一直存在，直到 XAsyncBlock 的完成回调被调用，然后可以将其删除。

> **重要：**
> **XAsyncBlock** 必须保留在内存中，直到**异步任务**完成。如果它是动态分配的，则可以在 XAsyncBlock 的**完成回调**内删除它。

### 等待**异步任务**

你可以通过多种不同的方式确定**异步任务**是否完成：

* XAsyncBlock 的**完成回调**被调用。
* 使用 true 调用 **XAsyncGetStatus** 以等待其完成。

使用 **XAsyncGetStatus** 时，**异步任务**在 XAsyncBlock 的**完成回调**执行后被视为完成，但 XAsyncBlock 的**完成回调**是可选的。

一旦**异步任务**完成，你就可以获取结果。

### 获取**异步任务**的结果

要获取结果，大多数**异步 API** 函数都有一个对应的 \[函数名]Result 函数，用于接收异步调用的结果。

在我们的示例代码中，**XblProfileGetUserProfileAsync** 有一个对应的 **XblProfileGetUserProfileResult** 函数。你可以使用此函数检索函数的结果并采取相应的操作。

有关检索结果的完整详细信息，请参阅每个**异步 API** 函数的文档。

## **XTaskQueueHandle**

**XTaskQueueHandle** 允许你确定哪个线程执行**异步任务**以及哪个线程调用 XAsyncBlock 的**完成回调**。

你可以通过设置*分派模式*来控制哪个线程执行这些操作。有三种分派模式可用：

* *Manual* - 手动队列不会自动分派。开发人员可以在他们想要的任何线程上分派它们。这可以用于将异步调用的工作端或回调端分配给特定线程。这将在下面更详细地讨论。

* *Thread Pool* - 使用线程池分派。线程池并行调用调用，当线程池线程可用时依次从队列中取出调用执行。这是最容易使用的，但对使用哪个线程的控制最少。

* *Serialized Thread Pool* - 使用线程池分派。线程池串行调用调用，当单个线程池线程可用时依次从队列中取出调用执行。

* *Immediate* - 立即在提交它的线程上分派排队的工作。

要创建新的 **XTaskQueueHandle**，你需要调用 **XTaskQueueCreate**。例如：

```cpp theme={null}
STDAPI XTaskQueueCreate(
    _In_ XTaskQueueDispatchMode workDispatchMode,
    _In_ XTaskQueueDispatchMode completionDispatchMode,
    _Out_ XTaskQueueHandle* queue
    ) noexcept;
```

此函数接受两个 `XTaskQueueDispatchMode` 参数。`XTaskQueueDispatchMode` 有三个可能的值：

```cpp theme={null}
/// <summary>
/// Describes how task queue callbacks are processed.
/// </summary>
enum class XTaskQueueDispatchMode : uint32_t
{
    /// <summary>
    /// Callbacks are invoked manually by XTaskQueueDispatch
    /// </summary>
    Manual,

    /// <summary>
    /// Callbacks are queued to the system thread pool and will
    /// be processed in order by the thread pool across multiple thread
    /// pool threads.
    /// </summary>
    ThreadPool,
    
    /// <summary>
    /// Callbacks are queued to the system thread pool and
    /// will be processed one at a time.
    /// </summary>
    SerializedThreadPool,
    
    /// <summary>
    /// Callbacks are not queued at all but are dispatched
    /// immediately by the thread that submits them.
    /// </summary>
    Immediate
};
```

**workDispatchMode** 确定处理异步工作的线程的分派模式，而 **completionDispatchMode** 确定处理异步操作完成的线程的分派模式。

一旦你创建了 **XTaskQueueHandle**，只需将其添加到 **XAsyncBlock** 即可控制工作和完成函数的线程。当你完成使用 **XTaskQueueHandle** 时（通常是在游戏结束时），你可以使用 **XTaskQueueCloseHandle** 关闭它：

```cpp theme={null}
STDAPI_(void) XTaskQueueCloseHandle(
    _In_ XTaskQueueHandle queue
    ) noexcept;
```

**调用示例**：

```cpp theme={null}
XTaskQueueCloseHandle(queue);
```

### 手动分派 **XTaskQueueHandle**

如果你对 **XTaskQueueHandle** 的工作或完成队列使用了手动队列分派模式，则需要手动分派。假设创建了一个 **XTaskQueueHandle**，其中工作队列和完成队列都设置为手动分派，如下所示：

```cpp theme={null}
XTaskQueueHandle queue = nullptr;
HRESULT hr = XTaskQueueCreate(
    XTaskQueueDispatchMode::Manual,
    XTaskQueueDispatchMode::Manual,
    &queue);
```

为了分派已分配 **XTaskQueueDispatchMode::Manual** 的工作，你需要使用 **XTaskQueueDispatch** 函数进行分派。

```cpp theme={null}
STDAPI_(bool) XTaskQueueDispatch(
    _In_ XTaskQueueHandle queue,
    _In_ XTaskQueuePort port,
    _In_ uint32_t timeoutInMs
    ) noexcept;
```

**调用示例**

```cpp theme={null}
HRESULT hr = XTaskQueueDispatch(queue, XTaskQueuePort::Completion, 0);
```

* *queue* - 要分派工作的队列。
* *port* - **XTaskQueuePort** 枚举的实例。
* *timeoutInMs* - 一个 uint32\_t，表示以毫秒为单位的超时。

**XTaskQueuePort** 枚举定义了两种回调类型：

```cpp theme={null}
/// <summary>
/// Declares which port of a task queue to dispatch or submit
/// callbacks to.
/// </summary>
enum class XTaskQueuePort : uint32_t
{
    /// <summary>
    /// Work callbacks
    /// </summary>
    Work,

    /// <summary>
    /// Completion callbacks after work is done
    /// </summary>
    Completion
};
```

### 何时调用 **XTaskQueueDispatch**

为了检查队列何时收到新项目，你可以调用 **XTaskQueueRegisterMonitor** 来设置事件处理程序，让你的代码知道工作或完成已准备好分派。

```cpp theme={null}
STDAPI XTaskQueueRegisterMonitor(
    _In_ XTaskQueueHandle queue,
    _In_opt_ void* callbackContext,
    _In_ XTaskQueueMonitorCallback* callback,
    _Out_ XTaskQueueRegistrationToken* token
    ) noexcept;
```

**XTaskQueueRegisterMonitor** 接受以下参数：

* *queue* - 你要提交回调的异步队列。
* *callbackContext* - 一个指针，指向应传递给提交回调的数据。
* *callback* - 当新的回调提交到队列时将被调用的函数。
* *token* - 一个令牌，将在稍后调用 **XTaskQueueUnregisterMonitor** 以移除回调时使用。

例如，以下是对 **XTaskQueueRegisterMonitor** 的调用：

`XTaskQueueRegisterMonitor(queue, nullptr, HandleAsyncQueueCallback, &m_callbackToken);`

对应的 **XTaskQueueMonitorCallback** 回调可以按如下方式实现：

```cpp theme={null}
void CALLBACK HandleAsyncQueueCallback(
    _In_opt_ void* context,
    _In_ XTaskQueueHandle queue,
    _In_ XTaskQueuePort port)
{
    switch (port)
    {
    case XTaskQueuePort::Work:
        {
            std::lock_guard<std::mutex> lock(g_workReadyMutex);
            g_workReady = true;
        }

        g_workReadyConditionVariable.notify_one(); // (std::condition_variable)
        break;
    }
}
```

然后在后台线程中，你可以监听此条件变量以唤醒并调用 **XTaskQueueDispatch**。

```cpp theme={null}
void BackgroundWorkThreadProc(XTaskQueueHandle queue)
{
    while (true)
    {
        {
            std::unique_lock<std::mutex> cvLock(g_workReadyMutex);
            g_workReadyConditionVariable.wait(cvLock, [] { return g_workReady; });

            if (g_stopBackgroundWork)
            {
                break;
            }

            g_workReady = false;
        }

        bool workFound = false;
        do
        {
            workFound = XTaskQueueDispatch(queue, XTaskQueuePort::Work, 0);
        } while (workFound);
    }
    
    XTaskQueueCloseHandle(queue);
}
```

## 另请参阅

[XSAPI C API 简介](/services/xbox-services/fundamentals/xbox-services-api/live-xsapi-flat-c)

[XSAPI 参考](/reference/live/xsapi-c/atoc-xsapi-c)

[libHttpClient](/reference/live/httpclient/atoc-httpclient)
