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

# Effectuer des appels asynchrones

> Effectuez des appels asynchrones dans le SDK C/C++ PlayFab Services à l'aide de XAsyncBlock, XTaskQueueHandle et des modes de distribution pour contrôler les threads.

Une API asynchrone est une API qui rend la main rapidement, mais qui démarre une tâche asynchrone dont le résultat est renvoyé une fois la tâche terminée.

Traditionnellement, les jeux ont peu de contrôle sur le thread qui exécute la tâche asynchrone et sur le thread qui renvoie les résultats lorsqu'un rappel d'achèvement est utilisé. Certains jeux sont conçus de sorte qu'une partie du tas ne soit manipulée que par un seul thread, afin d'éviter toute synchronisation entre threads. Si le rappel d'achèvement n'est pas appelé depuis un thread contrôlé par le jeu, la mise à jour de l'état partagé avec le résultat d'une tâche asynchrone nécessite une synchronisation des threads.

Le SDK PlayFab Services expose une API C asynchrone qui offre aux développeurs un contrôle direct des threads lors d'un appel d'API asynchrone, comme [**PFAuthenticationLoginWithCustomIDAsync**](/fr/services/playfab/api-references/c/pfauthentication/functions/pfauthenticationloginwithcustomidasync), [**PFDataGetFilesAsync**](/fr/services/playfab/api-references/c/pfdata/functions/pfdatagetfilesasync) ou [**PFProfilesGetProfileAsync**](/fr/services/playfab/api-references/c/pfprofiles/functions/pfprofilesgetprofileasync).

Voici un exemple de base d'appel à **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);
```

Pour comprendre ce modèle d'appel, vous devez comprendre comment utiliser **XAsyncBlock** et **XTaskQueueHandle**.

**XAsyncBlock** contient toutes les informations relatives à la tâche asynchrone et au rappel d'achèvement.

**XTaskQueueHandle** vous permet de déterminer quel thread exécute la tâche asynchrone et quel thread appelle le rappel d'achèvement de **XAsyncBlock**.

## XAsyncBlock

Examinons **XAsyncBlock** en détail. Il s'agit d'un struct défini comme suit :

```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** contient :

* *queue* : un **XTaskQueueHandle**, c'est-à-dire un handle représentant des informations sur l'emplacement d'exécution d'une tâche. Si ce paramètre n'est pas défini, une file d'attente par défaut est utilisée.
* *context* : vous permet de transmettre des données à la fonction de rappel.
* *callback* : une fonction de rappel facultative qui est appelée une fois le travail asynchrone terminé. Si vous ne spécifiez pas de rappel, vous pouvez attendre la fin de **XAsyncBlock** avec **XAsyncGetStatus**, puis obtenir les résultats.

Vous devez créer un nouveau **XAsyncBlock** sur le tas pour chaque appel asynchrone que vous effectuez. Le **XAsyncBlock** doit exister jusqu'à l'appel de son rappel d'achèvement ; il peut ensuite être supprimé.

> Important :
>
> Un **XAsyncBlock** doit rester en mémoire jusqu'à la fin de la tâche asynchrone. S'il est alloué dynamiquement, il peut être supprimé dans le rappel d'achèvement de **XAsyncBlock**.

### Attendre une tâche asynchrone

Vous pouvez savoir qu'une tâche asynchrone est terminée de deux manières :

* Le rappel d'achèvement de **XAsyncBlock** est appelé.
* Appelez **XAsyncGetStatus** avec true pour attendre sa fin.

Avec **XAsyncGetStatus**, la tâche asynchrone est considérée comme terminée après l'exécution du rappel d'achèvement de **XAsyncBlock** ; ce rappel d'achèvement est toutefois facultatif.

Une fois la tâche asynchrone terminée, vous pouvez obtenir les résultats.

### Obtenir le résultat de la tâche asynchrone

Pour obtenir le résultat, la plupart des fonctions d'API asynchrones disposent d'une fonction Result correspondante qui reçoit le résultat de l'appel asynchrone.

Dans notre exemple de code, **PFProfilesGetProfileAsync** dispose d'une fonction [**PFProfilesGetProfileGetResult**](/fr/services/playfab/api-references/c/pfprofiles/functions/pfprofilesgetprofilegetresult) correspondante. Vous pouvez utiliser cette fonction pour récupérer le résultat de la fonction et agir en conséquence.

Pour plus de détails sur la récupération des résultats, consultez la documentation de chaque fonction d'API asynchrone.

## XTaskQueueHandle

**XTaskQueueHandle** vous permet de déterminer quel thread exécute la tâche asynchrone et quel thread appelle le rappel d'achèvement de **XAsyncBlock**.

Vous pouvez contrôler le thread qui effectue ces opérations en définissant un mode de distribution. Les modes de distribution disponibles sont les suivants :

* *Manual* : la file d'attente manuelle n'est pas distribuée automatiquement. C'est au développeur de distribuer les éléments sur le thread de son choix. Ce mode permet d'affecter le côté travail ou le côté rappel d'un appel asynchrone à un thread spécifique.
* *Thread Pool* : distribue à l'aide d'un pool de threads. Le pool de threads appelle les éléments en parallèle, en prenant tour à tour un appel à exécuter dans la file d'attente à mesure que des threads du pool se libèrent. *Thread Pool* est le plus simple à utiliser, mais c'est celui qui vous offre le moins de contrôle sur le thread utilisé.
* *Serialized Thread Pool* : distribue à l'aide d'un pool de threads. Le pool de threads appelle les éléments en série, en prenant tour à tour un appel à exécuter dans la file d'attente à mesure que l'unique thread du pool se libère.
* *Immediate* : distribue immédiatement le travail mis en file d'attente sur le thread à partir duquel il a été soumis.

Pour créer un **XTaskQueueHandle**, vous devez appeler **XTaskQueueCreate**. Par exemple :

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

Cette fonction accepte deux paramètres **XTaskQueueDispatchMode**. **XTaskQueueDispatchMode** peut prendre trois valeurs :

```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** détermine le mode de distribution du thread qui gère le travail asynchrone. **completionDispatchMode** détermine le mode de distribution du thread qui gère l'achèvement de l'opération asynchrone.

Une fois votre **XTaskQueueHandle** créé, ajoutez-le simplement au **XAsyncBlock** pour contrôler les threads de vos fonctions de travail et d'achèvement. Lorsque vous avez fini d'utiliser le **XTaskQueueHandle**, généralement à la fin du jeu, vous pouvez le fermer avec **XTaskQueueCloseHandle** :

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

Exemple d'appel :

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

### Distribuer manuellement un XTaskQueueHandle

Si vous avez utilisé le mode de distribution manuel pour la file d'attente de travail ou d'achèvement d'un **XTaskQueueHandle**, vous devez effectuer la distribution manuellement. Supposons qu'un **XTaskQueueHandle** ait été créé avec la file d'attente de travail et la file d'attente d'achèvement toutes deux configurées pour une distribution manuelle, comme suit :

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

Pour distribuer le travail auquel **XTaskQueueDispatchMode**::Manual a été affecté, appelez la fonction **XTaskQueueDispatch**.

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

Exemple d'appel :

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

* *queue* : la file d'attente sur laquelle distribuer le travail.
* *port* : une instance de l'enum **XTaskQueuePort**.
* *timeoutInMs* : un uint32\_t indiquant le délai d'expiration en millisecondes.

L'enum **XTaskQueuePort** définit deux types de rappels :

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

### Quand appeler XTaskQueueDispatch

Pour savoir quand la file d'attente a reçu un nouvel élément, vous pouvez appeler **XTaskQueueRegisterMonitor** afin de définir un gestionnaire d'événements qui indique à votre code que du travail ou des achèvements sont prêts à être distribués.

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

**XTaskQueueRegisterMonitor** accepte les paramètres suivants :

* *queue* : la file d'attente asynchrone pour laquelle vous soumettez le rappel.
* *callbackContext* : un pointeur vers les données à transmettre au rappel de soumission.
* *callback* : la fonction appelée lorsqu'un nouveau rappel est soumis à la file d'attente.
* *token* : un jeton utilisé lors d'un appel ultérieur à **XTaskQueueUnregisterMonitor** pour supprimer le rappel.

Par exemple, voici un appel à **XTaskQueueRegisterMonitor** :

```cpp theme={null}
XTaskQueueRegisterMonitor(queue, nullptr, HandleAsyncQueueCallback, &m_callbackToken);
```

Le rappel **XTaskQueueMonitorCallback** correspondant peut être implémenté comme suit :

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

Ensuite, dans un thread d'arrière-plan, vous pouvez attendre le réveil de cette variable de condition et appeler **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);
}
```

## Référence

[Documentation de référence de l'API](/fr/services/playfab/api-references/c/pfauthentication/pfauthentication_members)


## Related topics

- [Effectuer des appels asynchrones dans l'API C de XSAPI](/fr/services/xbox-services/fundamentals/xbox-services-api/live-flatc-async-patterns.md)
- [Effectuer des appels asynchrones dans le SDK unifié PlayFab](/fr/services/playfab/sdks/unified-sdk/async-model.md)
- [XAsyncBlock](/fr/reference/system/xasync/structs/xasyncblock.md)
- [API des services XBOX](/fr/services/xbox-services/fundamentals/xbox-services-api/index.md)
- [Langages pris en charge par PlayFab](/fr/services/playfab/sdks/languages/index.md)
