Pular para o conteúdo principal

ValkarnTaskCompletionSource

ValkarnTaskCompletionSource<T> e o ValkarnTaskCompletionSource não-genérico dão a você controle manual sobre um ValkarnTask. São o equivalente assíncrono de escrever o resultado você mesmo — você mantém um objeto source, distribui seu .Task para chamadores e então resolve, falha ou cancela a partir de um local de chamada separado.

Este é o equivalente do Valkarn Tasks de TaskCompletionSource<T> da BCL, mas respaldado por ValkarnTask.Promise<T> para manter o modelo de alocação consistente com o resto da biblioteca.


ValkarnTaskCompletionSource<T>

public class ValkarnTaskCompletionSource<T>
{
public ValkarnTask<T> Task { get; }
public bool TrySetResult(T result);
public bool TrySetException(Exception ex);
public bool TrySetCanceled();
}

Task

public ValkarnTask<T> Task { get; }

A task que o código aguardante irá observar. Distribua-a para todos os chamadores que precisam esperar pelo resultado. Múltiplos chamadores podem fazer await na mesma task concorrentemente.

TrySetResult

public bool TrySetResult(T result);

Conclui a task com sucesso com o valor fornecido. Todas as continuações aguardantes são retomadas. Retorna true se a conclusão foi aceita; retorna false se a task já estava concluída (por qualquer chamada TrySet* anterior). Nunca lança exceção.

TrySetException

public bool TrySetException(Exception ex);

Causa falha na task com a exceção fornecida. O código aguardante recebe a exceção ao chamar await. Lança ArgumentNullException se ex é null. Retorna true se aceita, false se já concluída.

TrySetCanceled

public bool TrySetCanceled();

Cancela a task. O código aguardante recebe uma OperationCanceledException. Retorna true se aceita, false se já concluída.


ValkarnTaskCompletionSource não-genérico

Não há uma classe ValkarnTaskCompletionSource não-genérica separada na API pública. Para tasks manuais de retorno void, use ValkarnTask.Promise diretamente:

var promise = new ValkarnTask.Promise();
ValkarnTask task = promise.Task;

promise.TrySetResult(); // concluir
promise.TrySetException(ex);
promise.TrySetCanceled();

ValkarnTask.Promise expõe a mesma superfície TrySet* e a mesma proteção contra dupla conclusão, mas produz um ValkarnTask não-genérico em vez de um ValkarnTask<T>.


Proteção contra Dupla Conclusão

Todos os métodos TrySet* são seguros para chamar de qualquer thread a qualquer momento, incluindo concorrentemente. A primeira chamada que vence um compare-and-swap na máquina de estados interna tem sucesso; toda chamada subsequente retorna false e não tem efeito. Isso significa:

  • Chamar TrySetResult duas vezes não faz nada na segunda chamada.
  • Chamar TrySetResult após TrySetException não faz nada.
  • Duas threads competindo para concluir a mesma source simultaneamente é seguro — uma vence, a outra é silenciosamente ignorada.

Se você precisar saber qual chamador "venceu", verifique o valor de retorno. Se não se importar (sinal fire-and-forget), pode ignorá-lo com segurança.

// Corrida segura — apenas um destes irá realmente concluir a task
_ = source.TrySetResult(value);
_ = source.TrySetCanceled();

Padrões comuns

Fazendo bridge de uma API de callback

Muitas APIs Unity e de plataforma entregam resultados por callbacks em vez de async/await. ValkarnTaskCompletionSource<T> permite envolvê-las de forma limpa.

public ValkarnTask<Texture2D> LoadTextureAsync(string url)
{
var tcs = new ValkarnTaskCompletionSource<Texture2D>();

StartCoroutine(LoadCoroutine(url, tcs));

return tcs.Task;
}

IEnumerator LoadCoroutine(string url, ValkarnTaskCompletionSource<Texture2D> tcs)
{
var request = UnityWebRequestTexture.GetTexture(url);
yield return request.SendWebRequest();

if (request.result == UnityWebRequest.Result.Success)
tcs.TrySetResult(DownloadHandlerTexture.GetContent(request));
else
tcs.TrySetException(new Exception(request.error));
}

Os chamadores agora podem simplesmente fazer await na task retornada:

Texture2D tex = await LoadTextureAsync("https://example.com/image.png");

Sinal único (portão async)

Use ValkarnTask.Promise quando precisar de um sinal que dispara uma vez e desbloqueia qualquer número de waiters. Isso é semelhante a ManualResetEventSlim, mas nativo ao async.

public class AsyncGate
{
readonly ValkarnTask.Promise _promise = new();

// Qualquer número de chamadores pode aguardar isso
public ValkarnTask WaitAsync() => _promise.Task;

// Chamar uma vez para desbloquear todos
public void Open() => _promise.TrySetResult();
}

// Uso
var gate = new AsyncGate();

// Vários sistemas aguardam o portão independentemente
async ValkarnTask SystemAAsync()
{
await gate.WaitAsync();
// prosseguir após o portão abrir
}

async ValkarnTask SystemBAsync()
{
await gate.WaitAsync();
// prossegue ao mesmo tempo que SystemA
}

// Em algum outro lugar — abrir o portão
gate.Open();

Uma vez que TrySetResult() é chamado, todas as chamadas await atuais e futuras na task são concluídas imediatamente (de forma síncrona se a task já estiver concluída quando elas são executadas).

Envolvendo uma operação async de terceiros com suporte a cancelamento

public ValkarnTask<Result> RunWithTimeoutAsync(
Func<ValkarnTask<Result>> operation,
float timeoutSeconds,
CancellationToken ct)
{
var tcs = new ValkarnTaskCompletionSource<Result>();

RunCoreAsync(tcs, operation, timeoutSeconds, ct).Forget();

return tcs.Task;
}

async ValkarnTask RunCoreAsync(
ValkarnTaskCompletionSource<Result> tcs,
Func<ValkarnTask<Result>> operation,
float timeout,
CancellationToken ct)
{
using var linked = CancellationTokenSource.CreateLinkedTokenSource(ct);
linked.CancelAfter(TimeSpan.FromSeconds(timeout));

try
{
var result = await operation();
tcs.TrySetResult(result);
}
catch (OperationCanceledException)
{
tcs.TrySetCanceled();
}
catch (Exception ex)
{
tcs.TrySetException(ex);
}
}

Portão de inicialização adiada

Um padrão comum do Unity é expor uma task "pronta" que componentes podem aguardar independentemente de a inicialização ter começado ou não.

public class ServiceBus : MonoBehaviour
{
readonly ValkarnTask.Promise _readyPromise = new();

// Qualquer pessoa pode aguardar isso em qualquer ponto — antes ou depois de Initialize()
public ValkarnTask Ready => _readyPromise.Task;

async void Start()
{
await LoadConfigAsync();
await ConnectAsync();
_readyPromise.TrySetResult(); // desbloqueia todos os waiters
}
}

// Em qualquer outro componente
async ValkarnTask OnEnableAsync()
{
await ServiceBus.Instance.Ready; // aguarda se não estiver pronto, retorna instantaneamente se já estiver
DoWork();
}

Relação com ValkarnTask.Promise

ValkarnTaskCompletionSource<T> é um wrapper público fino em torno de ValkarnTask.Promise<T>. Ambos fornecem a mesma funcionalidade. A diferença é a convenção de nomenclatura:

TipoRetornaUso comum
ValkarnTaskCompletionSource<T>ValkarnTask<T>API pública, espelha o estilo BCL TaskCompletionSource<T>
ValkarnTask.Promise<T>ValkarnTask<T>Uso interno, ligeiramente mais direto
ValkarnTask.PromiseValkarnTaskSinais void (portões, eventos)

Todos os três respaldam sua task com ValkarnTaskCompletionCore<T>, a máquina de estados baseada em struct interna que lida com segurança de thread e a corrida de continuação/resultado usando um protocolo em duas fases baseado em CAS.