Pular para o conteúdo principal

ValkarnTask<T>

ValkarnTask<T> é o tipo de task assíncrona com retorno de valor no Valkarn Tasks. É uma readonly struct, carregando um resultado inline (quando concluída de forma síncrona) ou uma referência a um objeto source em pool (quando concluída de forma assíncrona).

Namespace: UnaPartidaMas.Valkarn.Tasks

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

Não há restrições genéricas em T. Qualquer tipo — tipo de valor, tipo de referência, struct ou class — é válido.


Criando instâncias

Tasks concluídas de forma síncrona

Esses métodos factory retornam um ValkarnTask<T> sem objeto source de apoio. Zero alocação.

ValkarnTask.FromResult<T>(T value)

Retorna um ValkarnTask<T> concluído carregando value inline. Declarado como método estático no tipo ValkarnTask não-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);

A struct retornada tem source == null. Aguardá-la não incorre em alocação de continuação — o compilador vê IsCompleted == true imediatamente.

ValkarnTask.FromException<T>(Exception exception)

Retorna um ValkarnTask<T> com falha. Aguardá-la relança a exceção com seu rastreamento de pilha original preservado via ExceptionDispatchInfo.

public static ValkarnTask<T> FromException<T>(Exception exception)
ValkarnTask<Texture2D> LoadTexture(string path)
{
if (string.IsNullOrEmpty(path))
return ValkarnTask.FromException<Texture2D>(
new ArgumentException("O caminho não deve estar vazio.", nameof(path)));

return LoadTextureAsync(path);
}

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

Retorna um ValkarnTask<T> cancelado. Aguardá-lo lança 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);
}

Via métodos async

Qualquer método async declarado para retornar ValkarnTask<T> usa AsyncValkarnTaskMethodBuilder<TResult> automaticamente:

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

O compilador gera uma máquina de estados. Se o método for concluído de forma síncrona (nunca suspende), AsyncValkarnTaskMethodBuilder<T>.Task retorna new ValkarnTask<T>(result) com source == null — zero alocação.


Executando trabalho no thread pool

Esses métodos executam um delegate no thread pool do .NET e retornam o resultado na thread principal (no PlayerLoopTiming especificado). São wrappers de conveniência sobre as variantes com nome mais longo RunOnThreadPool.

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

Executa um Func<T> síncrono no thread pool.

public static ValkarnTask<T> Run<T>(
Func<T> func,
PlayerLoopTiming timing = PlayerLoopTiming.Update,
CancellationToken cancellationToken = default)
// Computar no thread pool, resultado chega de volta na thread principal no próximo Update
int hash = await ValkarnTask.Run(() => ComputeExpensiveHash(data));

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

Executa um Func<ValkarnTask<T>> assíncrono no thread pool. Use quando o trabalho em si é assíncrono (ex.: I/O de arquivo).

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 as sobrecargas Run cancelam antecipadamente se o token já foi cancelado, e mudam de volta para a thread principal no timing após o trabalho ser concluído.


Membros de instância

IsCompleted

public bool IsCompleted { get; }

Retorna true se a task foi concluída em qualquer estado terminal (Succeeded, Faulted ou Canceled). Para tasks concluídas de forma síncrona (source == null), sempre retorna true sem despacho de interface.

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

GetStatus()

public ValkarnTask.Status GetStatus()

Retorna o ValkarnTask.Status atual. Valores possíveis: Pending, Succeeded, Faulted, Canceled. Para source == null, sempre retorna Succeeded.

GetAwaiter()

public Awaiter GetAwaiter()

Retorna uma struct Awaiter. Usado pelo compilador para implementar await. Você também pode chamá-lo diretamente para obter o resultado de forma síncrona (seguro apenas quando IsCompleted é true).

ValkarnTask<int> task = ValkarnTask.FromResult(10);
int value = task.GetAwaiter().GetResult(); // seguro — concluído de forma síncrona

Chamar GetResult() em uma task pendente lança InvalidOperationException.

AsNonGeneric()

public ValkarnTask AsNonGeneric()

Converte este ValkarnTask<T> em um ValkarnTask não-genérico, descartando o tipo de resultado. A task resultante compartilha a mesma source subjacente e token, portanto completa ao mesmo tempo.

ValkarnTask<int> typedTask = ComputeAsync();
ValkarnTask voidTask = typedTask.AsNonGeneric();
await voidTask; // aguarda conclusão, ignora resultado

Isso é útil ao passar tasks de tipos mistos para combinadores ou quando você se importa apenas com o timing da conclusão, não com o valor.


Combinadores retornando ValkarnTask<T>

WhenAll — sobrecarga tipada de duas tasks

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

Executa ambas as tasks concorrentemente e retorna uma tupla de resultados. Se qualquer task falhar ou for cancelada, a primeira exceção vence e o erro da outra é relatado por ValkarnTask.UnobservedException.

Desestruturação de tupla funciona naturalmente com a deconstrução do C#:

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

Caminho rápido de zero alocação: se ambas as tasks estiverem sincronamente concluídas no local de chamada, nenhum objeto em pool é criado e a tupla de resultado é retornada inline.

WhenAll — sobrecarga tipada de coleção

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

Aguarda todas as tasks na coleção concorrentemente e retorna um T[] em ordem 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);

Se a coleção estiver vazia, retorna ValkarnTask.FromResult(Array.Empty<T>()) — zero alocação. Se todas as tasks já estiverem sincronamente concluídas, um array de resultado é construído inline sem criar uma promise de combinador.

WhenAny — sobrecarga tipada de duas tasks

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

Retorna assim que qualquer task for concluída. A tupla de resultado contém o índice baseado em 0 do vencedor e seu valor. As tasks perdedoras continuam executando; seus erros (se houver) são relatados por ValkarnTask.UnobservedException. Cancelamentos de perdedores não são relatados intencionalmente.

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

if (winnerIndex == 0)
Debug.Log("Cache venceu");

WhenAny — sobrecarga tipada de coleção

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

Mesma semântica da sobrecarga de duas tasks, estendida para qualquer número de tasks. Requer pelo menos uma task; lança ArgumentException para coleções vazias.

var tasks = servers.Select(s => s.FetchAsync(query)).ToArray();
var (winnerIndex, data) = await ValkarnTask.WhenAny(tasks);
Debug.Log($"Servidor {winnerIndex} respondeu primeiro");

Métodos factory de conveniência

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

Invoca o delegate factory e aguarda a task resultante. Útil quando você quer adiar a construção de uma operação assíncrona.

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

Struct Awaiter (aninhada)

ValkarnTask<T>.Awaiter é o awaiter voltado ao compilador. É uma readonly struct implementando ICriticalNotifyCompletion. Normalmente você não interage com ela diretamente.

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

UnsafeOnCompleted é o caminho usado por AsyncValkarnTaskMethodBuilder<T>. O rótulo "unsafe" significa que ExecutionContext não é capturado — isso é intencional para Unity onde nenhum SynchronizationContext está em vigor.

Quando IsCompleted é true, chamar GetResult() lê o campo result inline (para tasks síncronas) ou chama pela interface source (para tasks assíncronas). Ambos os caminhos são [MethodImpl(MethodImplOptions.AggressiveInlining)].


Métodos de extensão em ValkarnTask<T>

AsResult<T>()

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

Envolve um ValkarnTask<T> em um ValkarnTask<Result<T>>, capturando qualquer exceção ou cancelamento e codificando-o no valor Result<T>. Isso evita try/catch no local de chamada.

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

Caminho rápido síncrono: se a task source já estiver sincronamente concluída, AsResult retorna de forma síncrona sem nenhuma maquinaria async envolvida.


Promise<T> — source de conclusão manual

ValkarnTask.Promise<T> é uma source de conclusão manual alocada no heap para casos em que você precisa controlar quando um ValkarnTask<T> é concluído e o tempo de vida não é limitado por uma única operação assíncrona.

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);
}
// Envolvendo uma API baseada em callback
var promise = new ValkarnTask.Promise<string>();

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

string result = await promise.Task;

Diferente de PooledPromise<T>, Promise<T> não está em pool. Usa um finalizador para detectar e relatar exceções não observadas se a task falhar e o chamador nunca a aguardar.

Para padrões de alta frequência (loops produtor/consumidor, operações por frame), prefira ValkarnTask.PooledPromise<T>, que retorna automaticamente ao pool após GetResult ser chamado.


PooledPromise<T> — source de conclusão manual em pool

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

Após GetResult ser chamado na task de apoio, a promise redefine seu ValkarnTaskCompletionCore<T> e retorna a si mesma ao pool. Uma proteção contra double-return garante que isso aconteça no máximo uma vez mesmo se GetResult for chamado concorrentemente.

// Padrão: produzir um ValkarnTask<T> que completa quando estiver pronto
var promise = ValkarnTask.PooledPromise<int>.Create(out uint token);
ValkarnTask<int> task = promise.Task;

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

// Consumidor aguarda; na conclusão a promise retorna ao pool automaticamente
int value = await task;

Resumo das formas de obter um ValkarnTask<T>

MétodoQuando usar
return value dentro de async ValkarnTask<T>Métodos async normais
ValkarnTask.FromResult(value)Retornos rápidos síncronos
ValkarnTask.FromException<T>(ex)Tasks pré-falhadas
ValkarnTask.FromCanceled<T>(ct)Tasks pré-canceladas
ValkarnTask.Run<T>(Func<T>, ...)Offloading para thread pool
ValkarnTask.Run<T>(Func<ValkarnTask<T>>, ...)Trabalho assíncrono no thread pool
ValkarnTask.WhenAll<T1,T2>(t1, t2)Aguardar duas tasks tipadas, obter tupla
ValkarnTask.WhenAll<T>(IEnumerable<...>)Aguardar N tasks tipadas, obter array
ValkarnTask.WhenAny<T>(t1, t2)Primeira de duas tasks tipadas
ValkarnTask.WhenAny<T>(IEnumerable<...>)Primeira de N tasks tipadas
task.AsResult<T>()Wrapper seguro contra exceções
new ValkarnTask.Promise<T>().TaskConclusão manual de longa duração
ValkarnTask.PooledPromise<T>.Create(...).TaskConclusão manual de alta frequência