> ## 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.

# 进行异步调用

> 使用 XAsyncBlock、XTaskQueueHandle 和调度模式在 PlayFab Services C/C++ SDK 中进行异步调用以进行线程控制。

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

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

PlayFab Services SDK 提供了一个异步 C API,当进行异步 API 调用(例如 [**PFAuthenticationLoginWithCustomIDAsync**](/services/playfab/api-references/c/pfauthentication/functions/pfauthenticationloginwithcustomidasync)、[**PFDataGetFilesAsync**](/services/playfab/api-references/c/pfdata/functions/pfdatagetfilesasync) 或 [**PFProfilesGetProfileAsync**](/services/playfab/api-references/c/pfprofiles/functions/pfprofilesgetprofileasync))时,可以让开发人员直接控制线程。

以下是调用 **PFProfilesGetProfileAsync** 的基本示例:

```cpp theme={null}
    XAsyncBlock* asyncBlock = new XAsyncBlock();
    asyncBlock->queue = GlobalState()->queue;
    asyncBlock->context = nullptr;
    asyncBlock->callback = [](XAsyncBlock* asyncBlock)
    {
        std::unique_ptr<XAsyncBlock> asyncBlockPtr{ asyncBlock }; // take ownership of XAsyncBlock
        
        size_t bufferSize;
        HRESULT hr = PFProfilesGetProfileGetResultSize(asyncBlock, &bufferSize);
        if (SUCCEEDED(hr))
        {
            std::vector<char> getProfileResultBuffer(bufferSize);
            PFProfilesGetEntityProfileResponse* getProfileResponseResult{ nullptr };
            PFProfilesGetProfileGetResult(asyncBlock, getProfileResultBuffer.size(), getProfileResultBuffer.data(), &getProfileResponseResult, nullptr);
        }
    };

    PFProfilesGetEntityProfileRequest profileRequest{};
    HRESULT hr = PFProfilesGetProfileAsync(GlobalState()->entityHandle, &profileRequest, 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** 完成,然后获取结果。

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

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

### 等待异步任务

你可以通过两种不同的方式知道异步任务已完成:

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

对于 **XAsyncGetStatus**,异步任务在 **XAsyncBlock** 的完成回调执行后才被认为已完成,但是 **XAsyncBlock** 的完成回调是可选的。

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

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

要获取结果,大多数异步 API 函数都有一个对应的 Result 函数来接收异步调用的结果。

在我们的示例代码中,**PFProfilesGetProfileAsync** 有一个对应的 [**PFProfilesGetProfileGetResult**](/services/playfab/api-references/c/pfprofiles/functions/pfprofilesgetprofilegetresult) 函数。你可以使用这个函数来检索该函数的结果并相应地采取行动。

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

## XTaskQueueHandle

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

你可以通过设置调度模式来控制哪个线程执行这些操作。有三种可用的调度模式:

* *Manual* - 手动队列不会自动调度。开发人员可以在他们想要的任何线程上调度它们。这可用于将异步调用的工作或回调端分配给特定线程。
* *Thread Pool* - 使用线程池进行调度。线程池并行调用,当线程池线程可用时,轮流从队列中取一个调用来执行。*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** 的调用:

```cpp theme={null}
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);
}
```

## 参考

[API 参考文档](/services/playfab/api-references/c/pfauthentication/pfauthentication_members)


## Related topics

- [在 XSAPI C API 中进行异步调用](/zh-CN/services/xbox-services/fundamentals/xbox-services-api/live-flatc-async-patterns.md)
- [在 PlayFab 统一 SDK 中进行异步调用](/zh-CN/services/playfab/sdks/unified-sdk/async-model.md)
- [Android 快速入门](/zh-CN/services/playfab/sdks/c/quickstart-android.md)
- [iOS 快速入门](/zh-CN/services/playfab/sdks/c/quickstart-ios.md)
- [Linux 快速入门](/zh-CN/services/playfab/sdks/c/quickstart-linux.md)
