c# TaskCompletionSource 的作用 c#怎么手动控制Task

来源:这里教程网 时间:2026-02-21 17:39:50 作者:

TaskCompletionSource 的核心作用:手动捏造一个可 await 的 Task

它不执行任何异步逻辑,也不启动线程——它只是给你一个

Task
实例的“遥控器”。你用
TaskCompletionSource<t></t>
创建出一个未完成的
Task<t></t>
,然后在任意时机(比如事件回调、第三方 SDK 通知、UI 线程响应后)调用
SetResult()
SetException()
SetCanceled()
,来强行“推”这个 Task 进入终态。这是把“回调驱动”转成“async/await 驱动”的最轻量级桥梁。

怎么手动控制 Task:三步走,缺一不可

常见错误是只创建了

TaskCompletionSource
却忘了暴露它的
Task
,或者在多线程环境下没注意线程安全调用
Set*
方法(虽然
Set*
本身是线程安全的,但业务逻辑可能不是)。

第一步:创建实例,声明你要返回的类型,比如
new TaskCompletionSource<bool>()</bool>
new TaskCompletionSource<string>()</string>
第二步:把
tcs.Task
返回出去,供调用方
await
;别直接 await
tcs
——它不是 Task
第三步:在真正该结束的时候(例如按钮点击、WebSocket 收到响应、Timer 触发),调用对应方法:
 • 成功 →
tcs.SetResult("done")

 • 失败 →
tcs.SetException(new InvalidOperationException("timeout"))

 • 取消 →
tcs.SetCanceled()
(注意:这会触发
OperationCanceledException

典型场景:包装 UI 弹窗、事件、老式 Begin/End 模式

比如你在 WPF 或 MAUI 中弹登录框,不能直接

await ShowDialog()
(它同步阻塞)。这时就用
TaskCompletionSource
桥接:

private TaskCompletionSource<LoginResult> _loginTcs;
public async Task<LoginResult> ShowLoginAsync()
{
    _loginTcs = new TaskCompletionSource<LoginResult>();
    var window = new LoginWindow();
    window.LoginCompleted += (result) => _loginTcs.SetResult(result); // 事件回调里推进
    window.Show();
    return await _loginTcs.Task;
}

⚠️ 注意:如果用户关掉窗口没触发事件,

_loginTcs.Task
就永远挂起——必须配超时或取消逻辑,否则会内存泄漏+死等。

容易踩的坑:重复 Set / 忘记 Set / 线程错乱

TaskCompletionSource
是一次性状态机:一旦调用了
SetResult
,再调一次就会抛
InvalidOperationException: "The task has already been completed."
。这不是 bug,是设计使然。

别在多个地方无保护地调用
Set*
——加
Interlocked.CompareExchange
或用
if (tcs.TrySetResult(...))
更安全(
TrySet*
系列方法会静默失败,适合竞态场景)
别漏掉异常路径:比如网络请求超时、事件没订阅成功、回调被 GC 掉,都可能导致 Task 永远不完成 不要在非 UI 线程直接操作 WPF/WinForms 控件后再调
SetResult
——先
Dispatcher.Invoke
BeginInvoke
回 UI 线程,再 Set

最常被忽略的一点:它和

CancellationToken
没有自动绑定。你想支持取消,得自己监听 token 并在
token.Register(() => tcs.TrySetCanceled())
,而不是指望
TaskCompletionSource
自动感知。

相关推荐