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

# Realización de llamadas asincrónicas en el SDK unificado de PlayFab

> Use XAsyncBlock y XTaskQueueHandle con el SDK unificado de PlayFab para controlar qué subprocesos ejecutan el trabajo asincrónico y las devoluciones de llamada de finalización en Core y Services.

Una API asincrónica es una API que se devuelve rápidamente pero inicia una tarea asincrónica, y el resultado se devuelve una vez finalizada la tarea.

Tradicionalmente, los juegos han tenido poco control sobre qué subproceso ejecuta la tarea asincrónica y qué subproceso devuelve los resultados al usar una devolución de llamada de finalización. Algunos juegos están diseñados de modo que una sección del montón solo la toque un único subproceso para evitar cualquier necesidad de sincronización de subprocesos. Si la devolución de llamada de finalización no se llama desde un subproceso que el juego controle, actualizar el estado compartido con el resultado de una tarea asincrónica requiere sincronización de subprocesos.

El SDK unificado de PlayFab expone una API asincrónica de C que ofrece a los desarrolladores control directo de los subprocesos al realizar una llamada asincrónica a la API.

Este es un ejemplo básico de una llamada a **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 comprender este patrón de llamada, necesita entender cómo usar **XAsyncBlock** y **XTaskQueueHandle**.

**XAsyncBlock** transporta toda la información relativa a la tarea asincrónica y la devolución de llamada de finalización.

**XTaskQueueHandle** le permite determinar qué subproceso ejecuta la tarea asincrónica y qué subproceso llama a la devolución de llamada de finalización de **XAsyncBlock**.

## XAsyncBlock

Veamos **XAsyncBlock** en detalle. Es una estructura definida de la siguiente manera:

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

* *queue*: un **XTaskQueueHandle**, que es un identificador que representa información sobre dónde ejecutar una parte del trabajo. Si este parámetro no está establecido, se usa una cola predeterminada.
* *context*: le permite pasar datos a la función de devolución de llamada.
* *callback*: una función de devolución de llamada opcional a la que se llamará una vez realizado el trabajo asincrónico. Si no especifica una devolución de llamada, puede esperar a que se complete el **XAsyncBlock** con **XAsyncGetStatus** y luego obtener los resultados.

Debería crear un nuevo **XAsyncBlock** en el montón por cada llamada asincrónica que realice. El **XAsyncBlock** debe existir hasta que se llame a la devolución de llamada de finalización del **XAsyncBlock**, y después se puede eliminar.

> Importante:
>
> Un **XAsyncBlock** debe permanecer en memoria hasta que se complete la tarea asincrónica. Si se asigna dinámicamente, se puede eliminar dentro de la devolución de llamada de finalización del **XAsyncBlock**.

### Espera de una tarea asincrónica

Puede saber que una tarea asincrónica se ha completado de dos maneras diferentes:

* Se llama a la devolución de llamada de finalización del **XAsyncBlock**.
* Llame a **XAsyncGetStatus** con true para esperar hasta que se complete.

Con **XAsyncGetStatus**, la tarea asincrónica se considera completada después de que se ejecute la devolución de llamada de finalización del **XAsyncBlock**; sin embargo, la devolución de llamada de finalización del **XAsyncBlock** es opcional.

Una vez completada la tarea asincrónica, puede obtener los resultados.

### Obtención del resultado de la tarea asincrónica

Para obtener el resultado, la mayoría de las funciones de API asincrónicas tienen una función Result correspondiente para recibir el resultado de la llamada asincrónica.

En nuestro código de ejemplo, **PFProfilesGetProfileAsync** tiene una función correspondiente **PFProfilesGetProfileGetResult**. Puede usar esta función para recuperar el resultado de la función y actuar en consecuencia.

Para obtener todos los detalles sobre la recuperación de resultados, consulte la documentación de cada función de API asincrónica.

## XTaskQueueHandle

**XTaskQueueHandle** le permite determinar qué subproceso ejecuta la tarea asincrónica y qué subproceso llama a la devolución de llamada de finalización del **XAsyncBlock**.

Puede controlar qué subproceso realiza estas operaciones estableciendo un modo de distribución. Hay tres modos de distribución disponibles:

* *Manual*: la cola manual no se distribuye automáticamente. Corresponde al desarrollador distribuirlas en el subproceso que quiera. Esto se puede usar para asignar el lado de trabajo o el lado de devolución de llamada de una llamada asincrónica a un subproceso específico.
* *Thread Pool*: distribuye mediante un grupo de subprocesos. El grupo de subprocesos invoca las llamadas en paralelo, tomando por turno una llamada de la cola para ejecutarla a medida que los subprocesos del grupo quedan disponibles. *Thread Pool* es el más fácil de usar, pero le ofrece el menor control sobre qué subproceso se usa.
* *Serialized Thread Pool*: distribuye mediante un grupo de subprocesos. El grupo de subprocesos invoca las llamadas en serie, tomando por turno una llamada de la cola para ejecutarla a medida que el único subproceso del grupo queda disponible.
* *Immediate*: distribuye inmediatamente el trabajo en cola en el subproceso desde el que se envió.

Para crear un nuevo **XTaskQueueHandle**, necesita llamar a **XTaskQueueCreate**. Por ejemplo:

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

Esta función toma dos parámetros **XTaskQueueDispatchMode**. Hay tres valores posibles 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 el modo de distribución para el subproceso que controla el trabajo asincrónico. **completionDispatchMode** determina el modo de distribución para el subproceso que controla la finalización de la operación asincrónica.

Una vez que haya creado su **XTaskQueueHandle**, simplemente agréguelo al **XAsyncBlock** para controlar los subprocesos de sus funciones de trabajo y de finalización. Cuando haya terminado de usar el **XTaskQueueHandle**, normalmente cuando el juego está finalizando, puede cerrarlo con **XTaskQueueCloseHandle**:

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

Ejemplo de llamada:

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

### Distribución manual de un XTaskQueueHandle

Si usó el modo de distribución de cola manual para una cola de trabajo o de finalización de un **XTaskQueueHandle**, debe distribuir manualmente. Supongamos que se creó un **XTaskQueueHandle** en el que tanto la cola de trabajo como la cola de finalización están configuradas para distribuirse manualmente, de esta manera:

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

Para distribuir el trabajo al que se ha asignado **XTaskQueueDispatchMode::Manual**, llame a la función **XTaskQueueDispatch**.

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

Ejemplo de llamada:

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

* *queue*: en qué cola distribuir el trabajo.
* *port*: una instancia de la enumeración **XTaskQueuePort**.
* *timeoutInMs*: un uint32\_t para el tiempo de espera en milisegundos.

Hay dos tipos de devolución de llamada definidos por la enumeración **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
};
```

### Cuándo llamar a XTaskQueueDispatch

Para comprobar cuándo la cola ha recibido un nuevo elemento, puede llamar a **XTaskQueueRegisterMonitor** para establecer un controlador de eventos que informe a su código de que hay trabajo o finalizaciones listos para distribuirse.

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

**XTaskQueueRegisterMonitor** toma los siguientes parámetros:

* *queue*: la cola asincrónica para la que envía la devolución de llamada.
* *callbackContext*: un puntero a los datos que deben pasarse a la devolución de llamada de envío.
* *callback*: la función que se invoca cuando se envía una nueva devolución de llamada a la cola.
* *token*: un token que se usa en una llamada posterior a **XTaskQueueUnregisterMonitor** para quitar la devolución de llamada.

Por ejemplo, esta es una llamada a **XTaskQueueRegisterMonitor**:

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

La devolución de llamada **XTaskQueueMonitorCallback** correspondiente podría implementarse de la siguiente manera:

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

Luego, en un subproceso en segundo plano, puede escuchar esta variable de condición para reactivarse y llamar a **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);
}
```

## Consulte también

* [Operaciones asincrónicas](/services/playfab/sdks/unified-sdk/async-model)
* [Administración de memoria](/services/playfab/sdks/unified-sdk/memory-management)
* [Seguimiento y diagnóstico](/services/playfab/sdks/unified-sdk/debug-trace)


## Related topics

- [Administración de memoria en el SDK unificado de PlayFab](/es/services/playfab/sdks/unified-sdk/memory-management.md)
- [SDK unificado de PlayFab](/es/services/playfab/sdks/unified-sdk/overview.md)
- [Seguimiento de depuración en el SDK unificado de PlayFab](/es/services/playfab/sdks/unified-sdk/debug-trace.md)
- [Implementación de Game Saves con el GDK de octubre de 2025](/es/services/playfab/player-progression/game-saves/october-2025-gdk-changes.md)
- [Migrar del SDK independiente v1 de PlayFab al SDK unificado v2](/es/services/playfab/sdks/unified-sdk/migrating-from-v1.md)
