跳到主要内容

ValkarnTaskCompletionSource

ValkarnTaskCompletionSource<T> 和非泛型的 ValkarnTaskCompletionSource 让你对 ValkarnTask 拥有手动控制权。它们是自己编写异步结果的等价物——你持有一个 source 对象,将其 .Task 分发给调用者,然后从另一个调用点解决、错误或取消它。

这是 Valkarn Tasks 对 BCL 中 TaskCompletionSource<T> 的等价实现,但由 ValkarnTask.Promise<T> 后备,以保持与库其余部分一致的分配模型。


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

等待代码将观察到的任务。将其分发给所有需要等待结果的调用者。多个调用者可以并发 await 同一个任务。

TrySetResult

public bool TrySetResult(T result);

以给定值成功完成任务。所有等待中的续体恢复。如果完成被接受,返回 true;如果任务已经完成(通过任何先前的 TrySet* 调用),返回 false。永不抛出。

TrySetException

public bool TrySetException(Exception ex);

以提供的异常使任务错误。等待代码在调用 await 时收到异常。如果 exnull,抛出 ArgumentNullException。如果被接受,返回 true;如果已完成,返回 false

TrySetCanceled

public bool TrySetCanceled();

取消任务。等待代码收到 OperationCanceledException。如果被接受,返回 true;如果已完成,返回 false


非泛型 ValkarnTaskCompletionSource

公共 API 中没有独立的非泛型 ValkarnTaskCompletionSource 类。对于 void 返回的手动任务,直接使用 ValkarnTask.Promise

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

promise.TrySetResult(); // 完成
promise.TrySetException(ex);
promise.TrySetCanceled();

ValkarnTask.Promise 公开了相同的 TrySet* 接口和相同的双重完成保护,但产生非泛型 ValkarnTask 而不是 ValkarnTask<T>


双重完成保护

所有 TrySet* 方法都可以从任意线程在任意时间安全调用,包括并发调用。第一个在内部状态机上赢得 compare-and-swap 的调用成功;每个后续调用返回 false 且无效果。这意味着:

  • 两次调用 TrySetResult 时,第二次无效。
  • TrySetException 之后调用 TrySetResult 无效。
  • 两个线程同时竞争完成同一个 source 是安全的——一个赢,一个被静默忽略。

如果你需要知道哪个调用者"赢了",检查返回值。如果你不在乎(即发即弃信号),可以安全地忽略它。

// 安全竞争——只有其中一个会真正完成任务
_ = source.TrySetResult(value);
_ = source.TrySetCanceled();

常见模式

桥接回调 API

许多 Unity 和平台 API 通过回调而不是 async/await 传递结果。ValkarnTaskCompletionSource<T> 让你可以干净地包装它们。

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

调用者现在可以简单地 await 返回的任务:

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

一次性信号(异步门)

当你需要一个触发一次并解除任意数量等待者的信号时,使用 ValkarnTask.Promise。这类似于 ManualResetEventSlim,但是原生异步的。

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

// 任意数量的调用者都可以等待这个
public ValkarnTask WaitAsync() => _promise.Task;

// 调用一次以解除所有人的阻塞
public void Open() => _promise.TrySetResult();
}

// 使用
var gate = new AsyncGate();

// 几个系统独立等待门
async ValkarnTask SystemAAsync()
{
await gate.WaitAsync();
// 门打开后继续
}

async ValkarnTask SystemBAsync()
{
await gate.WaitAsync();
// 与 SystemA 同时继续
}

// 在其他地方——打开门
gate.Open();

一旦调用了 TrySetResult(),所有当前和未来对任务的 await 调用都立即完成(如果任务在它们运行时已完成,则同步完成)。

包装带取消支持的第三方异步操作

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

延迟初始化门

一个常见的 Unity 模式是公开一个"就绪"任务,无论初始化是否已开始,组件都可以等待它。

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

// 任何人都可以在任何时间等待这个——在 Initialize() 之前或之后
public ValkarnTask Ready => _readyPromise.Task;

async void Start()
{
await LoadConfigAsync();
await ConnectAsync();
_readyPromise.TrySetResult(); // 解除所有等待者
}
}

// 在任何其他组件中
async ValkarnTask OnEnableAsync()
{
await ServiceBus.Instance.Ready; // 如果未就绪则等待,如果已就绪则立即返回
DoWork();
}

与 ValkarnTask.Promise 的关系

ValkarnTaskCompletionSource<T>ValkarnTask.Promise<T> 的薄公共包装。两者提供相同的功能。区别在于命名约定:

类型返回常见用途
ValkarnTaskCompletionSource<T>ValkarnTask<T>公共 API,镜像 BCL TaskCompletionSource<T> 风格
ValkarnTask.Promise<T>ValkarnTask<T>内部使用,更直接
ValkarnTask.PromiseValkarnTaskVoid 信号(门、事件)

所有三者都用 ValkarnTaskCompletionCore<T> 作为其任务的后备,这是内部的基于结构体的状态机,使用基于 CAS 的两阶段协议处理线程安全和续体/结果竞争。