> ## 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 は、[**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) などの非同期 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**](/services/playfab/api-references/c/pfprofiles/functions/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);
}
```

## リファレンス

[API リファレンスドキュメント](/services/playfab/api-references/c/pfauthentication/pfauthentication_members)


## Related topics

- [PlayFab がサポートする言語](/ja-jp/services/playfab/sdks/languages/index.md)
- [XBOX services API](/ja-jp/services/xbox-services/fundamentals/xbox-services-api/index.md)
- [XSAPI C API で非同期呼び出しを行う](/ja-jp/services/xbox-services/fundamentals/xbox-services-api/live-flatc-async-patterns.md)
- [PlayFab 統合 SDK での非同期呼び出し](/ja-jp/services/playfab/sdks/unified-sdk/async-model.md)
- [XAsyncBlock](/ja-jp/reference/system/xasync/structs/xasyncblock.md)
