> ## 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 タイトルにおける XblProfileGetUserProfileAsync などの XSAPI C の非同期呼び出しに対するスレッド処理を制御します。

**非同期 API** とは、素早く戻る API のことで、**非同期タスク**を開始し、タスクが終了した後に結果が返されます。

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

XSAPI C API は、**XblSocialGetSocialRelationshipsAsync()**、**XblProfileGetUserProfileAsync()**、**XblAchievementsGetAchievementsForTitleIdAsync()** のような**非同期 API** 呼び出しを行う際に、開発者に直接的なスレッド制御を提供する新しい非同期 C API を公開します。

以下は **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 の**完了コールバック**が呼び出される。
* **XAsyncGetStatus** を true で呼び出して、完了するまで待機する。

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

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

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

結果を取得するには、ほとんどの**非同期 API** 関数には、対応する \[Name of Function]Result 関数があり、非同期呼び出しの結果を受け取ります。

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

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

## **XTaskQueueHandle**

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

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

* *Manual* - 手動キューは自動的にディスパッチされません。開発者が任意のスレッドでディスパッチする必要があります。これは、非同期呼び出しの作業側またはコールバック側のいずれかを特定のスレッドに割り当てるために使用できます。これについては後ほど詳しく説明します。

* *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** の呼び出しは次のようになります:

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


## Related topics

- [クイックスタート (Windows) - PlayFab サービスの呼び出し](/ja-jp/services/playfab/sdks/unified-sdk/quickstart-services.md)
- [非同期呼び出しの実行](/ja-jp/services/playfab/sdks/c/async.md)
- [PlayFab 統合 SDK での非同期呼び出し](/ja-jp/services/playfab/sdks/unified-sdk/async-model.md)
- [XAsyncBlock](/ja-jp/reference/system/xasync/structs/xasyncblock.md)
- [非同期タスク プログラミング向け XAsync ライブラリ](/ja-jp/build/core-features/common/async/async-libraries/index.md)
