Pular para o conteúdo principal

Funcionalidades

Referência completa de funcionalidades para Valkarn Tasks.


Primitivo async principal​

Struct ValkarnTask​

Um tipo de retorno async baseado em struct, sem alocações, que substitui tanto UniTask quanto o Awaitable do Unity.

async ValkarnTask LoadLevel() { ... }
async ValkarnTask<int> CountEnemies() { ... }
  • Caminho rápido síncrono sem alocação — se o método completar sem nunca suspender, zero alocações de heap ocorrem.
  • Caminho async com pool — se o método suspender, o runner da máquina de estados é retirado de um pool limitado e redimensionável. Zero boxing, pools limitados com trim automático.
  • Pooling IL2CPP-first — operações de pool na thread principal usam zero atômicos. O IL2CPP penaliza Volatile em 9,2× e Interlocked em 2,9× vs Mono; Valkarn evita ambos no caminho quente.
  • Segurança de token geracional — um contador de geração uint por slot de pool. 4.294.967.296 ciclos por slot antes de colisão — impossível na prática. (UniTask usa um token short: colisão após ~18 minutos de trabalho async ativo.)

Result<T> — tratamento de erros sem exceções​

var result = await loadTask.AsResult();

if (result.Succeeded) Use(result.Value);
else if (result.IsCanceled) ShowRetry();
else Debug.LogError(result.Error);

Result<T> e Result são valores readonly struct que representam o resultado de uma tarefa sem lançar exceções. Ambos suportam conversão implícita para bool (verdadeiro quando bem-sucedido).

Fontes de conclusão manual​

var promise = new ValkarnTask.Promise<string>();
promise.TrySetResult("done");
string value = await promise.Task;

Suporta TrySetResult, TrySetException e TrySetCanceled. O relatório de exceções não observadas baseado em finalizer garante que os erros nunca sejam perdidos silenciosamente.

Fontes de conclusão com pool​

Variantes com pool de auto-reset para padrões repetitivos (usadas internamente por canais e combinadores):

var source = ValkarnTask.PooledPromise<int>.Create(out uint token);
source.TrySetResult(42);
int value = await source.Task; // source retorna ao pool automaticamente

Bridge para Awaitable​

Interoperabilidade transparente com o Awaitable do Unity — sem conversão manual:

async ValkarnTask LoadGame()
{
await SceneManager.LoadSceneAsync("Level2"); // Unity Awaitable — funciona diretamente
await Resources.LoadAsync<Texture2D>("hero"); // Unity Awaitable — funciona diretamente
await ValkarnTask.Delay(1000); // nativo do Valkarn
}

O gerador de código-fonte detecta awaits de Awaitable e gera o adaptador automaticamente.


Cancelamento de ciclo de vida​

Automático (gerado por código-fonte)​

Marque a classe como partial — o gerador de código-fonte faz o resto:

public partial class EnemySpawner : MonoBehaviour
{
async ValkarnTask Start()
{
while (true)
{
await ValkarnTask.Delay(3000);
SpawnWave();
// Cancelado automaticamente quando este GameObject é destruído.
}
}
}
  • MonoBehaviour — vinculado ao destroyCancellationToken
  • ScriptableObject — vinculado ao tempo de vida da aplicação
  • Classe simples — sem vinculação automática (token manual necessário)

Desativar​

[NoAutoCancel]
async ValkarnTask BackgroundSync()
{
await SyncToServer(); // não é auto-cancelado na destruição
}

Override manual de CancellationToken​

Passar um CancellationToken explícito substitui o token de ciclo de vida injetado automaticamente:

async ValkarnTask DoWork(CancellationToken ct)
{
await ValkarnTask.Delay(1000, ct);
}

Sem cancelamento de siblings​

Tarefas nunca cancelam tarefas irmãs. WhenAll aguarda TODAS as tarefas; WhenAny retorna o primeiro resultado, mas as tarefas perdedoras continuam executando. Isso previne corrupção de dados quando as tarefas têm efeitos colaterais.


Seções críticas​

Para operações que não devem ser interrompidas pelo cancelamento de ciclo de vida:

async ValkarnTask SaveProgress()
{
var data = await LoadPlayerData(); // cancelável

await using (ValkarnTask.Critical())
{
await db.Insert(data); // NÃO cancelado mesmo se o GO for destruído
await db.Commit();
} // cancelamento pendente é aplicado aqui

await SendNotification(); // cancelável novamente
}

Dentro de uma seção crítica, o cancelamento é adiado — não ignorado. Quando a seção termina, o cancelamento pendente é aplicado.


Combinadores​

WhenAll (tipado)​

// Direto — lança exceção se alguma tarefa falhar
var (enemies, map) = await ValkarnTask.WhenAll(LoadEnemies(), LoadMap());

// Seguro — encapsule com AsResult() para comportamento sem lançamento
var (a, b) = await ValkarnTask.WhenAll(
LoadEnemies().AsResult(), LoadMap().AsResult());

Caminho rápido síncrono sem alocação quando todas as tarefas já estão concluídas. A sobrecarga IEnumerable<ValkarnTask<T>> usa ArrayPool<T> para arrays internos.

WhenAll (void)​

await ValkarnTask.WhenAll(SaveA(), SaveB(), SaveC());
await ValkarnTask.WhenAll(taskList); // IEnumerable<ValkarnTask>

WhenAny​

var (winnerIndex, result) = await ValkarnTask.WhenAny(DownloadFromA(), DownloadFromB());

Retorna o primeiro resultado concluído. As tarefas perdedoras continuam executando naturalmente.

Fire-and-forget​

SendAnalytics("event").Forget();

[FireAndForget]
async ValkarnTask SendAnalytics(string eventName) { ... }
// Chamadores não precisam de .Forget() — nenhum aviso gerado

.Forget() encaminha erros para ValkarnTask.UnobservedException. Nunca engolido silenciosamente.

AsNonGeneric​

ValkarnTask voidTask = typedTask.AsNonGeneric();

Tempo e delay​

await ValkarnTask.Delay(1000);                                    // milissegundos
await ValkarnTask.Delay(TimeSpan.FromSeconds(2)); // TimeSpan
await ValkarnTask.Delay(1000, DelayType.UnscaledDeltaTime); // ignorar timescale
await ValkarnTask.Delay(1000, DelayType.Realtime); // baseado em Stopwatch

await ValkarnTask.Yield(); // próximo tick do PlayerLoop
await ValkarnTask.Yield(PlayerLoopTiming.FixedUpdate); // temporização específica
await ValkarnTask.NextFrame(); // garantidamente no próximo frame
await ValkarnTask.DelayFrame(5); // N frames

await ValkarnTask.WaitUntil(() => player.IsReady);
await ValkarnTask.WaitWhile(() => isLoading);

Troca de thread​

async ValkarnTask ProcessData()
{
var raw = await DownloadData();

await ValkarnTask.SwitchToThreadPool();
var processed = HeavyComputation(raw); // thread de fundo

await ValkarnTask.SwitchToMainThread();
ApplyToGameObject(processed); // thread principal
}

16 timings do PlayerLoop​

GrupoTimings
InitializationInitialization, LastInitialization
EarlyUpdateEarlyUpdate, LastEarlyUpdate
FixedUpdateFixedUpdate, LastFixedUpdate
PreUpdatePreUpdate, LastPreUpdate
UpdateUpdate, LastUpdate
PreLateUpdatePreLateUpdate, LastPreLateUpdate
PostLateUpdatePostLateUpdate, LastPostLateUpdate
TimeUpdateTimeUpdate, LastTimeUpdate

Todas as operações usam PlayerLoopTiming.Update por padrão, salvo indicação em contrário.


Canais​

// Ilimitado
var channel = ValkarnTask.Channel.CreateUnbounded<EnemySpawnRequest>();

// Limitado — backpressure quando cheio
var channel = ValkarnTask.Channel.CreateBounded<LogEntry>(capacity: 100);

// Multi-consumidor
var channel = ValkarnTask.Channel.CreateUnbounded<WorkItem>(multiConsumer: true);

// Produtor
await channel.Writer.WriteAsync(entry);
bool accepted = channel.Writer.TryWrite(entry);

// Consumidor
await foreach (var item in channel.Reader.ReadAllAsync())
Process(item);

// Conclusão
channel.Writer.Complete();
await channel.Reader.Completion;

Testes determinísticos (TestClock)​

[Test]
public void Respawn_WaitsThreeSeconds()
{
var clock = new TestClock();
var task = spawner.RespawnEnemy();

clock.Advance(TimeSpan.FromSeconds(2));
Assert.IsFalse(task.IsCompleted);

clock.Advance(TimeSpan.FromSeconds(1));
Assert.IsTrue(task.IsCompleted);
}

Todas as operações dependentes de tempo leem de TimeProvider.Current. Em testes, substitua pelo TestClock. AdvanceFrame() simula um único tick do player loop.


Bridge para Job System​

var job = new PathfindingJob { start = a, end = b, results = results };
await job.ScheduleAsync();
// NativeArray de results é legível imediatamente após o await

await job.ScheduleParallelAsync(dataCount, batchSize); // IJobParallelFor

// Cancelamento — completa o job handle antes de reportar o cancelamento (sem vazamento de job)
await job.ScheduleAsync(cancellationToken);

Diagnósticos em tempo de compilação​

CódigoSeveridadeDescrição
TT001AvisoDouble-await / use-after-free em um ValkarnTask
TT002ErroResultado de async ValkarnTask usado como declaração de expressão — deve ser aguardado ou .Forget()
TT010InfoMétodo async em MonoBehaviour auto-cancelado no Destroy
TT011AvisoWhenAll contém tarefas com ciclos de vida diferentes
TT012AvisoLoop async sem verificação de cancelamento (possível loop zumbi)
TT013AvisoValkarnTask retornado mas nunca aguardado e não explicitamente descartado
TT014Aviso[NoAutoCancel] sem parâmetro manual CancellationToken
TT015InfoAguardando Awaitable dentro de ValkarnTask — adaptador bridge gerado
TT016AvisoMétodo async sem expressão await
TT017Aviso[FireAndForget] em ValkarnTask<T> — descarta valor de retorno

Gerenciamento de pool​

Todo runner de método async é colocado em pool via ValkarnPool<T>:

  • Thread principal — stack sem lock, zero atômicos
  • Threads de fundo — Treiber lock-free stack com operações CAS
  • Trim baseado em frame — a cada 300 frames (~5s a 60fps), objetos em excesso são liberados gradualmente
  • Nunca reduz abaixo do mínimo configurável (padrão: 8)

Monitore em tempo de execução:

foreach (var (type, size, maxSize) in ValkarnTask.GetPoolInfo())
Debug.Log($"{type}: {size}/{maxSize}");

ValkarnTaskSettings​

Configure via ScriptableObject (Assets > Create > Valkarn > Tasks > Task Settings, coloque em Resources/):

ConfiguraçãoPadrãoDescrição
DefaultMaxPoolSize256Máximo de itens por tipo de pool
MinPoolSize8Nunca reduzir abaixo deste valor
TrimCheckInterval300Frames entre verificações de trim
TrimHysteresisCount2Verificações consecutivas antes do trim
TrimReleaseRatio0,25Fração do excesso liberada por ciclo
EnableAutoCanceltrueAuto-vincular tarefas MonoBehaviour ao destroyCancellationToken
LogUnobservedCancellationsfalseRegistrar cancelamentos não observados como avisos
MaxExceptionLogsPerFrame10Limite de logs de exceção por frame

Tratamento de erros​

// Exceções não observadas — disparadas deterministicamente no retorno ao pool
ValkarnTask.UnobservedException += ex =>
Debug.LogError($"Unobserved: {ex}");
  • WhenAll — lança a primeira exceção, encaminha as adicionais para UnobservedException
  • WhenAny — a exceção do vencedor é lançada; as falhas dos perdedores vão para UnobservedException
  • Cancelamento de ciclo de vida — OperationCanceledException suprimida por padrão (configurável)

Métodos de fábrica​

ValkarnTask.CompletedTask          // void, zero-alloc
ValkarnTask.FromResult<T>(value) // tipado, zero-alloc
ValkarnTask.FromException(ex) // com falha
ValkarnTask.FromException<T>(ex)
ValkarnTask.FromCanceled() // cancelado
ValkarnTask.FromCanceled<T>()
ValkarnTask.Never // nunca completa (sentinela para WhenAny)