Saltar al contenido principal

ValkarnTaskCompletionSource

ValkarnTaskCompletionSource<T> y el ValkarnTaskCompletionSource no genérico te dan control manual sobre un ValkarnTask. Son el equivalente async de escribir el resultado tú mismo — tienes un objeto fuente, entregas su .Task a los llamadores, y luego lo resuelves, fallas o cancelas desde un punto de llamada separado.

Este es el equivalente de Valkarn Tasks de TaskCompletionSource<T> de la BCL, pero respaldado por ValkarnTask.Promise<T> para mantener el modelo de asignación consistente con el resto de la 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; }

La tarea que observará el código que espera. Distribúyela a todos los llamadores que necesiten esperar el resultado. Múltiples llamadores pueden hacer await en la misma tarea concurrentemente.

TrySetResult

public bool TrySetResult(T result);

Completa la tarea exitosamente con el valor dado. Todas las continuaciones en espera se reanudan. Devuelve true si el completado fue aceptado; devuelve false si la tarea ya estaba completada (por cualquier llamada TrySet* anterior). Nunca lanza.

TrySetException

public bool TrySetException(Exception ex);

Falla la tarea con la excepción proporcionada. El código en espera recibe la excepción cuando llama a await. Lanza ArgumentNullException si ex es null. Devuelve true si fue aceptado, false si ya estaba completado.

TrySetCanceled

public bool TrySetCanceled();

Cancela la tarea. El código en espera recibe una OperationCanceledException. Devuelve true si fue aceptado, false si ya estaba completado.


ValkarnTaskCompletionSource no genérico

No hay una clase ValkarnTaskCompletionSource no genérica separada en la API pública. Para tareas manuales que devuelven void, usa ValkarnTask.Promise directamente:

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

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

ValkarnTask.Promise expone la misma superficie TrySet* y la misma protección de doble completado, pero produce un ValkarnTask no genérico en lugar de un ValkarnTask<T>.


Protección contra Doble Completado

Todos los métodos TrySet* son seguros para llamar desde cualquier hilo en cualquier momento, incluyendo concurrentemente. La primera llamada que gana una comparación e intercambio en la máquina de estados interna tiene éxito; cada llamada posterior devuelve false y no tiene ningún efecto. Esto significa:

  • Llamar a TrySetResult dos veces no hace nada en la segunda llamada.
  • Llamar a TrySetResult después de TrySetException no hace nada.
  • Dos hilos compitiendo para completar la misma fuente simultáneamente es seguro — uno gana, el otro se ignora silenciosamente.

Si necesitas saber qué llamador "ganó", verifica el valor de retorno. Si no te importa (señal fire-and-forget), puedes ignorarlo con seguridad.

// Carrera segura — solo una de estas completará realmente la tarea
_ = source.TrySetResult(value);
_ = source.TrySetCanceled();

Patrones Comunes

Puentear una API de callbacks

Muchas APIs de Unity y de plataforma entregan resultados a través de callbacks en lugar de async/await. ValkarnTaskCompletionSource<T> te permite envolverlos de forma limpia.

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

Los llamadores ahora pueden simplemente hacer await en la tarea devuelta:

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

Señal de un solo disparo (puerta async)

Usa ValkarnTask.Promise cuando necesitas una señal que se dispare una vez y desbloquee cualquier número de esperadores. Esto es similar a un ManualResetEventSlim pero nativo de async.

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

// Cualquier número de llamadores puede esperar esto
public ValkarnTask WaitAsync() => _promise.Task;

// Llamar una vez para desbloquear a todos
public void Open() => _promise.TrySetResult();
}

// Uso
var gate = new AsyncGate();

// Varios sistemas esperan la puerta de forma independiente
async ValkarnTask SystemAAsync()
{
await gate.WaitAsync();
// proceder después de que la puerta se abra
}

async ValkarnTask SystemBAsync()
{
await gate.WaitAsync();
// procede al mismo tiempo que SystemA
}

// En algún otro lugar — abrir la puerta
gate.Open();

Una vez que se llama a TrySetResult(), todas las llamadas a await actuales y futuras en la tarea se completan inmediatamente (sincrónicamente si la tarea ya está hecha cuando se ejecutan).

Envolver una operación async de terceros con soporte de cancelación

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

Puerta de inicialización diferida

Un patrón común de Unity es exponer una tarea "lista" que los componentes pueden esperar independientemente de si la inicialización ya ha comenzado.

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

// Cualquiera puede esperar esto en cualquier momento — antes o después de Initialize()
public ValkarnTask Ready => _readyPromise.Task;

async void Start()
{
await LoadConfigAsync();
await ConnectAsync();
_readyPromise.TrySetResult(); // desbloquea todos los esperadores
}
}

// En cualquier otro componente
async ValkarnTask OnEnableAsync()
{
await ServiceBus.Instance.Ready; // espera si no está listo, devuelve instantáneamente si ya está listo
DoWork();
}

Relación con ValkarnTask.Promise

ValkarnTaskCompletionSource<T> es un envoltorio público delgado alrededor de ValkarnTask.Promise<T>. Ambos proporcionan la misma funcionalidad. La diferencia es la convención de nombres:

TipoDevuelveUso común
ValkarnTaskCompletionSource<T>ValkarnTask<T>API pública, refleja el estilo BCL TaskCompletionSource<T>
ValkarnTask.Promise<T>ValkarnTask<T>Uso interno, ligeramente más directo
ValkarnTask.PromiseValkarnTaskSeñales void (puertas, eventos)

Los tres respaldan su tarea con un ValkarnTaskCompletionCore<T>, la máquina de estados interna basada en struct que maneja la seguridad de hilos y la carrera continuación/resultado usando un protocolo de dos fases basado en CAS.