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

# PlayFab 統合 SDK での非同期呼び出し

> PlayFab 統合 SDK で XAsyncBlock と XTaskQueueHandle を使用して、Core および Services 全体で非同期処理と完了コールバックを実行するスレッドを制御します。

非同期 API とは、すぐに応答を返し、非同期タスクを開始する API のことで、タスクが完了した後に結果が返されます。

従来、ゲームは完了コールバックを使用する際に、どのスレッドが非同期タスクを実行し、どのスレッドが結果を返すかをほとんど制御できませんでした。一部のゲームは、スレッド同期の必要性を回避するために、ヒープの一部を単一のスレッドのみが操作するように設計されています。ゲームが制御するスレッドから完了コールバックが呼び出されない場合、非同期タスクの結果で共有状態を更新するにはスレッド同期が必要になります。

PlayFab 統合 SDK は、非同期 API 呼び出しを行う際に開発者に直接的なスレッド制御を提供する、非同期 C API を公開しています。

以下は **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** の完了コールバック内で削除できます。

### 非同期タスクの待機

非同期タスクが完了したことを確認する方法は 2 つあります:

* **XAsyncBlock** の完了コールバックが呼び出される。
* **XAsyncGetStatus** を true で呼び出して、完了するまで待機する。

**XAsyncGetStatus** では、**XAsyncBlock** の完了コールバックが実行された後に非同期タスクは完了したと見なされます。ただし、**XAsyncBlock** の完了コールバックはオプションです。

非同期タスクが完了したら、結果を取得できます。

### 非同期タスクの結果の取得

結果を取得するには、ほとんどの非同期 API 関数に、非同期呼び出しの結果を受け取るための対応する Result 関数があります。

サンプル コードでは、**PFProfilesGetProfileAsync** には対応する **PFProfilesGetProfileGetResult** 関数があります。この関数を使用して、関数の結果を取得し、それに応じて動作させることができます。

結果の取得に関する詳細については、各非同期 API 関数のドキュメントを参照してください。

## XTaskQueueHandle

**XTaskQueueHandle** を使用すると、どのスレッドが非同期タスクを実行し、どのスレッドが **XAsyncBlock** の完了コールバックを呼び出すかを決定できます。

ディスパッチ モードを設定することで、これらの操作を実行するスレッドを制御できます。利用可能なディスパッチ モードは 3 つあります:

* *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;
```

この関数は 2 つの **XTaskQueueDispatchMode** パラメーターを取ります。**XTaskQueueDispatchMode** には 3 つの可能な値があります:

```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** 列挙型で定義されるコールバック タイプは 2 つあります:

```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);
}
```

## 関連項目

* [非同期操作](/services/playfab/sdks/unified-sdk/async-model)
* [メモリ管理](/services/playfab/sdks/unified-sdk/memory-management)
* [トレースと診断](/services/playfab/sdks/unified-sdk/debug-trace)
