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

# Fazendo chamadas assíncronas

> Faça chamadas assíncronas no SDK do PlayFab Services para C/C++ usando XAsyncBlock, XTaskQueueHandle e modos de despacho para controlar as threads.

Uma API assíncrona é uma API que retorna rapidamente, mas inicia uma tarefa assíncrona, e o resultado é retornado depois que a tarefa é concluída.

Tradicionalmente, os jogos têm pouco controle sobre qual thread executa a tarefa assíncrona e qual thread retorna os resultados ao usar um callback de conclusão. Alguns jogos são projetados para que uma seção do heap seja acessada por apenas uma thread, a fim de evitar qualquer necessidade de sincronização de threads. Se o callback de conclusão não for chamado a partir de uma thread controlada pelo jogo, atualizar o estado compartilhado com o resultado de uma tarefa assíncrona exigirá sincronização de threads.

O SDK do PlayFab Services expõe uma API C assíncrona que dá aos desenvolvedores controle direto das threads ao fazer uma chamada à API assíncrona, como [**PFAuthenticationLoginWithCustomIDAsync**](/pt-BR/services/playfab/api-references/c/pfauthentication/functions/pfauthenticationloginwithcustomidasync), [**PFDataGetFilesAsync**](/pt-BR/services/playfab/api-references/c/pfdata/functions/pfdatagetfilesasync) ou [**PFProfilesGetProfileAsync**](/pt-BR/services/playfab/api-references/c/pfprofiles/functions/pfprofilesgetprofileasync).

Aqui está um exemplo básico que chama **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);
```

Para entender esse padrão de chamada, você precisa entender como usar o **XAsyncBlock** e o **XTaskQueueHandle**.

O **XAsyncBlock** carrega todas as informações relacionadas à tarefa assíncrona e ao callback de conclusão.

O **XTaskQueueHandle** permite determinar qual thread executa a tarefa assíncrona e qual thread chama o callback de conclusão do **XAsyncBlock**.

## O XAsyncBlock

Vamos examinar o **XAsyncBlock** em detalhes. Ele é uma struct definida da seguinte forma:

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

O **XAsyncBlock** contém:

* *queue* - um **XTaskQueueHandle**, que é um identificador que representa informações sobre onde executar uma parte do trabalho. Se esse parâmetro não for definido, uma fila padrão será usada.
* *context* - permite passar dados para a função de callback.
* *callback* - uma função de callback opcional que será chamada depois que o trabalho assíncrono for concluído. Se você não especificar um callback, poderá aguardar a conclusão do **XAsyncBlock** com **XAsyncGetStatus** e, em seguida, obter os resultados.

Você deve criar um novo **XAsyncBlock** no heap para cada chamada assíncrona que fizer. O **XAsyncBlock** precisa existir até que o callback de conclusão do **XAsyncBlock** seja chamado; depois disso, ele pode ser excluído.

> Importante:
>
> Um **XAsyncBlock** precisa permanecer na memória até que a tarefa assíncrona seja concluída. Se ele for alocado dinamicamente, poderá ser excluído dentro do callback de conclusão do **XAsyncBlock**.

### Aguardando uma tarefa assíncrona

Você pode saber que uma tarefa assíncrona foi concluída de duas maneiras diferentes:

* O callback de conclusão do **XAsyncBlock** é chamado.
* Chame **XAsyncGetStatus** com true para aguardar até que ela seja concluída.

Com **XAsyncGetStatus**, a tarefa assíncrona é considerada concluída depois que o callback de conclusão do **XAsyncBlock** é executado; no entanto, o callback de conclusão do **XAsyncBlock** é opcional.

Depois que a tarefa assíncrona for concluída, você poderá obter os resultados.

### Obtendo o resultado da tarefa assíncrona

Para obter o resultado, a maioria das funções de API assíncronas tem uma função Result correspondente para receber o resultado da chamada assíncrona.

No nosso código de exemplo, **PFProfilesGetProfileAsync** tem uma função [**PFProfilesGetProfileGetResult**](/pt-BR/services/playfab/api-references/c/pfprofiles/functions/pfprofilesgetprofilegetresult) correspondente. Você pode usar essa função para recuperar o resultado da função e agir de acordo.

Para obter detalhes completos sobre como recuperar resultados, consulte a documentação de cada função de API assíncrona.

## O XTaskQueueHandle

O **XTaskQueueHandle** permite determinar qual thread executa a tarefa assíncrona e qual thread chama o callback de conclusão do **XAsyncBlock**.

Você pode controlar qual thread executa essas operações definindo um modo de despacho. Há três modos de despacho disponíveis:

* *Manual* - A fila manual não é despachada automaticamente. Cabe ao desenvolvedor despachá-la em qualquer thread que desejar. Isso pode ser usado para atribuir o lado de trabalho ou o lado de callback de uma chamada assíncrona a uma thread específica.
* *Thread Pool* - Despacha usando um pool de threads. O pool de threads invoca as chamadas em paralelo, retirando da fila uma chamada para executar à medida que as threads do pool ficam disponíveis. O *Thread Pool* é o mais fácil de usar, mas oferece o menor controle sobre qual thread é usada.
* *Serialized Thread Pool* - Despacha usando um pool de threads. O pool de threads invoca as chamadas em série, retirando da fila uma chamada para executar à medida que a única thread do pool fica disponível.
* *Immediate* - Despacha imediatamente o trabalho enfileirado na thread a partir da qual ele foi enviado.

Para criar um novo **XTaskQueueHandle**, você precisa chamar **XTaskQueueCreate**. Por exemplo:

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

Essa função recebe dois parâmetros **XTaskQueueDispatchMode**. Há três valores possíveis para **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** determina o modo de despacho da thread que processa o trabalho assíncrono. **completionDispatchMode** determina o modo de despacho da thread que processa a conclusão da operação assíncrona.

Depois de criar seu **XTaskQueueHandle**, basta adicioná-lo ao **XAsyncBlock** para controlar as threads das suas funções de trabalho e de conclusão. Quando terminar de usar o **XTaskQueueHandle**, normalmente quando o jogo estiver terminando, você poderá fechá-lo com **XTaskQueueCloseHandle**:

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

Exemplo de chamada:

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

### Despachando manualmente um XTaskQueueHandle

Se você usou o modo de despacho de fila manual para uma fila de trabalho ou de conclusão de um **XTaskQueueHandle**, precisará despachá-la manualmente. Digamos que um **XTaskQueueHandle** tenha sido criado com a fila de trabalho e a fila de conclusão definidas para despacho manual, assim:

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

Para despachar o trabalho que recebeu **XTaskQueueDispatchMode::Manual**, chame a função **XTaskQueueDispatch**.

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

Exemplo de chamada:

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

* *queue* - a fila na qual o trabalho será despachado.
* *port* - uma instância do enum **XTaskQueuePort**.
* *timeoutInMs* - um uint32\_t para o tempo limite em milissegundos.

Há dois tipos de callback definidos pelo enum **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
};
```

### Quando chamar XTaskQueueDispatch

Para verificar quando a fila recebeu um novo item, você pode chamar **XTaskQueueRegisterMonitor** para definir um manipulador de eventos que informe ao seu código que há trabalhos ou conclusões prontos para serem despachados.

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

**XTaskQueueRegisterMonitor** recebe os seguintes parâmetros:

* *queue* - a fila assíncrona para a qual você está enviando o callback.
* *callbackContext* - um ponteiro para os dados que devem ser passados para o callback de envio.
* *callback* - a função invocada quando um novo callback é enviado para a fila.
* *token* - um token usado em uma chamada posterior a **XTaskQueueUnregisterMonitor** para remover o callback.

Por exemplo, aqui está uma chamada a **XTaskQueueRegisterMonitor**:

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

O callback **XTaskQueueMonitorCallback** correspondente pode ser implementado da seguinte forma:

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

Em seguida, em uma thread em segundo plano, você pode aguardar que essa variável de condição seja acionada e chamar **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);
}
```

## Referência

[Documentação de referência da API](/pt-BR/services/playfab/api-references/c/pfauthentication/pfauthentication_members)


## Related topics

- [Fazendo chamadas assíncronas na API C da XSAPI](/pt-BR/services/xbox-services/fundamentals/xbox-services-api/live-flatc-async-patterns.md)
- [Fazendo chamadas assíncronas no SDK unificado do PlayFab](/pt-BR/services/playfab/sdks/unified-sdk/async-model.md)
- [Linguagens compatíveis com o PlayFab](/pt-BR/services/playfab/sdks/languages/index.md)
- [API dos serviços do XBOX](/pt-BR/services/xbox-services/fundamentals/xbox-services-api/index.md)
- [SDK do PlayFab Services](/pt-BR/services/playfab/sdks/c/index.md)
