TaskCompletionSource的核心作用是手动创建可await的Task,通过SetResult/SetException/SetCanceled控制其完成状态,实现回调到async/await的轻量桥接。
它不执行任何异步逻辑,也不启动线程——它只是给你一个 Task 实例的“遥控器”。你用 TaskCompletionSource 创建出一个未完成的 Task,然后在任意时机(比如事件回调、第三方 SDK 通知、UI 线程响应后)调用 SetResult()、SetException() 或 SetCanceled(),来强行“推”这个 Task 进入终态。这是把“回调驱动”转成“async/await 驱动”的最轻量级桥梁。
常见错误是只创建了 TaskCompletionSource 却忘了暴露它的 Task,或者在多线程环境下没注意线程安全调用 Set* 方法(虽然 Set* 本身是线程安全的,但业务逻辑可能不是)。
new TaskCompletionSource() 或 new TaskCompletionSource()
tcs.Task 返回出去,供调用方 await;别直接 await tcs ——它不是 Tasktcs.SetResult("do
ne")tcs.SetException(new InvalidOperationException("timeout"))tcs.SetCanceled()(注意:这会触发 OperationCanceledException)比如你在 WPF 或 MAUI 中弹登录框,不能直接 await ShowDialog()(它同步阻塞)。这时就用 TaskCompletionSource 桥接:
private TaskCompletionSource_loginTcs; public async Task ShowLoginAsync() { _loginTcs = new TaskCompletionSource (); var window = new LoginWindow(); window.LoginCompleted += (result) => _loginTcs.SetResult(result); // 事件回调里推进 window.Show(); return await _loginTcs.Task; }
⚠️ 注意:如果用户关掉窗口没触发事件,_loginTcs.Task 就永远挂起——必须配超时或取消逻辑,否则会内存泄漏+死等。
TaskCompletionSource 是一次性状态机:一旦调用了 SetResult,再调一次就会抛 InvalidOperationException: "The task has already been completed."。这不是 bug,是设计使然。
Set* ——加 Interlocked.CompareExchange 或用 if (tcs.TrySetResult(...)) 更安全(TrySet* 系列方法会静默失败,适合竞态场景)SetResult ——先 Dispatcher.Invoke 或 BeginInvoke 回 UI 线程,再 Set最常被忽略的一点:它和 CancellationToken 没有自动绑定。你想支持取消,得自己监听 token 并在 token.Register(() => tcs.TrySetCanceled()),而不是指望 TaskCompletionSource 自动感知。