Saltar al contenido principal

ValkarnTask<T>

ValkarnTask<T> es el tipo de tarea async que devuelve valores en Valkarn Tasks. Es un readonly struct, que lleva un resultado en línea (cuando se completa sincrónicamente) o una referencia a un objeto fuente agrupado (cuando se completa asincrónicamente).

Espacio de nombres: UnaPartidaMas.Valkarn.Tasks

[AsyncMethodBuilder(typeof(CompilerServices.AsyncValkarnTaskMethodBuilder<>))]
[StructLayout(LayoutKind.Auto)]
public readonly struct ValkarnTask<T>

No hay restricciones genéricas en T. Cualquier tipo — tipo de valor, tipo de referencia, struct o clase — es válido.


Crear instancias

Tareas completadas sincrónicamente

Estos métodos de fábrica devuelven un ValkarnTask<T> sin objeto fuente de respaldo. Cero asignaciones.

ValkarnTask.FromResult<T>(T value)

Devuelve un ValkarnTask<T> completado que lleva value en línea. Declarado como método estático en el tipo ValkarnTask no genérico.

public static ValkarnTask<T> FromResult<T>(T value)
ValkarnTask<int> task = ValkarnTask.FromResult(42);
ValkarnTask<string> name = ValkarnTask.FromResult("Valkarn");
ValkarnTask<Vector3> pos = ValkarnTask.FromResult(transform.position);

El struct devuelto tiene source == null. Esperarlo no incurre en asignación de continuación — el compilador ve IsCompleted == true inmediatamente.

ValkarnTask.FromException<T>(Exception exception)

Devuelve un ValkarnTask<T> fallido. Esperarlo relanza la excepción con su traza de pila original preservada vía ExceptionDispatchInfo.

public static ValkarnTask<T> FromException<T>(Exception exception)
ValkarnTask<Texture2D> LoadTexture(string path)
{
if (string.IsNullOrEmpty(path))
return ValkarnTask.FromException<Texture2D>(
new ArgumentException("La ruta no debe estar vacía.", nameof(path)));

return LoadTextureAsync(path);
}

ValkarnTask.FromCanceled<T>(CancellationToken ct = default)

Devuelve un ValkarnTask<T> cancelado. Esperarlo lanza OperationCanceledException.

public static ValkarnTask<T> FromCanceled<T>(CancellationToken ct = default)
ValkarnTask<byte[]> Download(string url, CancellationToken ct)
{
if (ct.IsCancellationRequested)
return ValkarnTask.FromCanceled<byte[]>(ct);

return DownloadAsync(url, ct);
}

Vía métodos async

Cualquier método async declarado para devolver ValkarnTask<T> usa AsyncValkarnTaskMethodBuilder<TResult> automáticamente:

async ValkarnTask<int> ComputeAsync()
{
await ValkarnTask.Yield();
return 42;
}

El compilador genera una máquina de estados. Si el método se completa sincrónicamente (nunca se suspende), AsyncValkarnTaskMethodBuilder<T>.Task devuelve new ValkarnTask<T>(result) con source == null — cero asignaciones.


Ejecutar trabajo en el grupo de hilos

Estos métodos ejecutan un delegado en el grupo de hilos de .NET y devuelven el resultado en el hilo principal (en el PlayerLoopTiming especificado). Son envoltorios de conveniencia sobre las variantes con nombre más largo RunOnThreadPool.

ValkarnTask.Run<T>(Func<T> func, PlayerLoopTiming timing, CancellationToken ct)

Ejecuta un Func<T> síncrono en el grupo de hilos.

public static ValkarnTask<T> Run<T>(
Func<T> func,
PlayerLoopTiming timing = PlayerLoopTiming.Update,
CancellationToken cancellationToken = default)
// Calcular en el grupo de hilos, resultado llega de vuelta en el hilo principal en el próximo Update
int hash = await ValkarnTask.Run(() => ComputeExpensiveHash(data));

ValkarnTask.Run<T>(Func<ValkarnTask<T>> func, PlayerLoopTiming timing, CancellationToken ct)

Ejecuta un Func<ValkarnTask<T>> async en el grupo de hilos. Úsalo cuando el trabajo en sí es async (por ejemplo, E/S de archivos).

public static ValkarnTask<T> Run<T>(
Func<ValkarnTask<T>> func,
PlayerLoopTiming timing = PlayerLoopTiming.Update,
CancellationToken cancellationToken = default)
string json = await ValkarnTask.Run(async () =>
{
using var reader = File.OpenText("data.json");
return await reader.ReadToEndAsync();
});

Ambas sobrecargas Run cancelan temprano si el token ya está cancelado, y cambian de vuelta al hilo principal en timing después de que el trabajo se completa.


Miembros de instancia

IsCompleted

public bool IsCompleted { get; }

Devuelve true si la tarea se ha completado en cualquier estado terminal (Succeeded, Faulted o Canceled). Para tareas completadas sincrónicamente (source == null), siempre devuelve true sin despacho de interfaz.

var task = SomeLongOperation();
if (task.IsCompleted)
{
int result = task.GetAwaiter().GetResult();
Use(result);
}

GetStatus()

public ValkarnTask.Status GetStatus()

Devuelve el ValkarnTask.Status actual. Valores posibles: Pending, Succeeded, Faulted, Canceled. Para source == null, siempre devuelve Succeeded.

GetAwaiter()

public Awaiter GetAwaiter()

Devuelve un struct Awaiter. Usado por el compilador para implementar await. También puedes llamarlo directamente para obtener el resultado sincrónicamente (solo seguro cuando IsCompleted es verdadero).

ValkarnTask<int> task = ValkarnTask.FromResult(10);
int value = task.GetAwaiter().GetResult(); // seguro — completado sincrónicamente

Llamar a GetResult() en una tarea pendiente lanza una InvalidOperationException.

AsNonGeneric()

public ValkarnTask AsNonGeneric()

Convierte este ValkarnTask<T> a un ValkarnTask no genérico, descartando el tipo de resultado. La tarea resultante comparte el mismo source subyacente y token, por lo que se completa al mismo tiempo.

ValkarnTask<int> typedTask = ComputeAsync();
ValkarnTask voidTask = typedTask.AsNonGeneric();
await voidTask; // espera la completación, ignora el resultado

Esto es útil cuando se pasan tareas de tipos mixtos a combinadores o cuando solo te importa el timing de completado, no el valor.


Combinadores que devuelven ValkarnTask<T>

WhenAll — sobrecarga tipada de dos tareas

public static ValkarnTask<(T1, T2)> WhenAll<T1, T2>(
ValkarnTask<T1> task1, ValkarnTask<T2> task2)

Ejecuta ambas tareas concurrentemente y devuelve una tupla de resultados. Si cualquier tarea falla o se cancela, la primera excepción gana y el error de la otra se reporta a través de ValkarnTask.UnobservedException.

La desestructuración de tuplas funciona naturalmente con la deconstrucción de C#:

var (profile, inventory) = await ValkarnTask.WhenAll(
FetchProfileAsync(userId),
FetchInventoryAsync(userId)
);

Camino rápido de cero asignaciones: si ambas tareas están completadas sincrónicamente en el punto de llamada, no se crea ningún objeto agrupado y la tupla de resultado se devuelve en línea.

WhenAll — sobrecarga de colección tipada

public static ValkarnTask<T[]> WhenAll<T>(IEnumerable<ValkarnTask<T>> tasks)

Espera todas las tareas en la colección concurrentemente y devuelve un T[] en orden de índice.

var urls = new[] { "https://a.com", "https://b.com", "https://c.com" };
ValkarnTask<string>[] downloads = urls.Select(u => DownloadAsync(u)).ToArray();
string[] results = await ValkarnTask.WhenAll(downloads);

Si la colección está vacía, devuelve ValkarnTask.FromResult(Array.Empty<T>()) — cero asignaciones. Si todas las tareas ya están completadas sincrónicamente, se construye un array de resultado en línea sin crear una promesa de combinador.

WhenAny — sobrecarga tipada de dos tareas

public static ValkarnTask<(int winnerIndex, T result)> WhenAny<T>(
ValkarnTask<T> task1, ValkarnTask<T> task2)

Retorna tan pronto como cualquiera de las tareas se completa. La tupla de resultado contiene el índice basado en 0 del ganador y su valor. Las tareas perdedoras siguen ejecutándose; sus errores (si los hay) se reportan a través de ValkarnTask.UnobservedException. Las cancelaciones de los perdedores intencionalmente no se reportan.

var (winnerIndex, result) = await ValkarnTask.WhenAny(
FetchFromCacheAsync(key),
FetchFromNetworkAsync(key)
);

if (winnerIndex == 0)
Debug.Log("El caché ganó");

WhenAny — sobrecarga de colección tipada

public static ValkarnTask<(int winnerIndex, T result)> WhenAny<T>(
IEnumerable<ValkarnTask<T>> tasks)

Misma semántica que la sobrecarga de dos tareas, extendida a cualquier número de tareas. Requiere al menos una tarea; lanza ArgumentException para colecciones vacías.

var tasks = servers.Select(s => s.FetchAsync(query)).ToArray();
var (winnerIndex, data) = await ValkarnTask.WhenAny(tasks);
Debug.Log($"El servidor {winnerIndex} respondió primero");

Métodos de fábrica de conveniencia

ValkarnTask.Create<T>(Func<ValkarnTask<T>> factory)

Invoca el delegado de fábrica y espera la tarea resultante. Útil cuando quieres diferir la construcción de una operación async.

public static async ValkarnTask<T> Create<T>(Func<ValkarnTask<T>> factory)
var result = await ValkarnTask.Create(() => LoadLevelDataAsync(levelId));

Struct Awaiter (anidado)

ValkarnTask<T>.Awaiter es el awaiter orientado al compilador. Es un readonly struct que implementa ICriticalNotifyCompletion. Normalmente no interactúas con él directamente.

public readonly struct Awaiter : ICriticalNotifyCompletion
{
public bool IsCompleted { get; }
public T GetResult();
public void OnCompleted(Action continuation);
public void UnsafeOnCompleted(Action continuation);
}

UnsafeOnCompleted es el camino usado por AsyncValkarnTaskMethodBuilder<T>. La etiqueta "unsafe" significa que ExecutionContext no se captura — esto es intencional para Unity donde no hay SynchronizationContext en efecto.

Cuando IsCompleted es verdadero, llamar a GetResult() lee el campo result en línea (para tareas síncronas) o llama a través de la interfaz fuente (para tareas asíncronas). Ambos caminos son [MethodImpl(MethodImplOptions.AggressiveInlining)].


Métodos de extensión en ValkarnTask<T>

AsResult<T>()

public static ValkarnTask<Result<T>> AsResult<T>(this ValkarnTask<T> task)

Envuelve un ValkarnTask<T> en un ValkarnTask<Result<T>>, capturando cualquier excepción o cancelación y codificándola en el valor Result<T>. Esto evita try/catch en el punto de llamada.

Result<string> result = await FetchDataAsync(url).AsResult();

if (result.IsSuccess)
Process(result.Value);
else if (result.IsCanceled)
Debug.Log("Cancelado");
else
Debug.LogError(result.Exception);

Camino rápido síncrono: si la tarea fuente ya está completada sincrónicamente, AsResult devuelve sincrónicamente sin maquinaria async involucrada.


Promise<T> — fuente de completado manual

ValkarnTask.Promise<T> es una fuente de completado manual asignada en el montón para casos donde necesitas controlar cuándo se completa un ValkarnTask<T>, y el tiempo de vida no está acotado por una única operación async.

public class Promise<T>
{
public ValkarnTask<T> Task { get; }
public bool TrySetResult(T result);
public bool TrySetException(Exception ex);
public bool TrySetCanceled(CancellationToken ct = default);
}
// Envolver una API basada en callbacks
var promise = new ValkarnTask.Promise<string>();

SomeCallbackApi.OnComplete += value => promise.TrySetResult(value);
SomeCallbackApi.OnError += ex => promise.TrySetException(ex);

string result = await promise.Task;

A diferencia de PooledPromise<T>, Promise<T> no está agrupado. Usa un finalizador para detectar y reportar excepciones no observadas si la tarea falla y el llamador nunca la espera.

Para patrones de alta frecuencia (bucles productor/consumidor, operaciones por fotograma), prefiere ValkarnTask.PooledPromise<T>, que vuelve automáticamente al grupo después de que se llama a GetResult.


PooledPromise<T> — fuente de completado manual agrupada

public sealed class PooledPromise<T> : ValkarnTask.ISource<T>, IPoolNode<PooledPromise<T>>
{
public static PooledPromise<T> Create(out uint token);
public static PooledPromise<T> CreateCompleted(T result, out uint token);
public ValkarnTask<T> Task { get; }
public bool TrySetResult(T result);
public bool TrySetException(Exception ex);
public bool TrySetCanceled(CancellationToken ct = default);
}

Después de que GetResult se llama en la tarea de respaldo, la promesa reinicia su ValkarnTaskCompletionCore<T> y se devuelve al grupo. Una protección de doble retorno asegura que esto ocurra como máximo una vez incluso si GetResult se llama concurrentemente.

// Patrón: producir un ValkarnTask<T> que se completa cuando esté listo
var promise = ValkarnTask.PooledPromise<int>.Create(out uint token);
ValkarnTask<int> task = promise.Task;

// Despachar trabajo de forma asíncrona
ThreadPool.QueueUserWorkItem(_ =>
{
int result = DoWork();
promise.TrySetResult(result);
});

// El consumidor espera; al completarse la promesa vuelve al grupo automáticamente
int value = await task;

Resumen de formas de obtener un ValkarnTask<T>

MétodoCuándo usarlo
return value dentro de async ValkarnTask<T>Métodos async normales
ValkarnTask.FromResult(value)Retornos rápidos síncronos
ValkarnTask.FromException<T>(ex)Tareas pre-fallidas
ValkarnTask.FromCanceled<T>(ct)Tareas pre-canceladas
ValkarnTask.Run<T>(Func<T>, ...)Descarga al grupo de hilos
ValkarnTask.Run<T>(Func<ValkarnTask<T>>, ...)Trabajo async en grupo de hilos
ValkarnTask.WhenAll<T1,T2>(t1, t2)Esperar dos tareas tipadas, obtener tupla
ValkarnTask.WhenAll<T>(IEnumerable<...>)Esperar N tareas tipadas, obtener array
ValkarnTask.WhenAny<T>(t1, t2)Primera de dos tareas tipadas
ValkarnTask.WhenAny<T>(IEnumerable<...>)Primera de N tareas tipadas
task.AsResult<T>()Envolvimiento seguro ante excepciones
new ValkarnTask.Promise<T>().TaskCompletado manual de larga duración
ValkarnTask.PooledPromise<T>.Create(...).TaskCompletado manual de alta frecuencia