C# TaskCompletionSource使用方法 C#如何将回调模式转换为Task

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

TaskCompletionSource 是什么,为什么不用直接 new Task

TaskCompletionSource
不是用来“创建运行中的任务”,而是用来“手动控制一个
Task
的完成状态”。它背后没有线程、不调度、不执行任何逻辑,只提供
SetResult
SetException
SetCanceled
这三个方法来终结其关联的
Task
。直接 new
Task
后调用
Start()
会触发线程调度,且无法从外部决定完成时机;而回调转
Task
的核心诉求恰恰是“等外部信号来了再结束”,所以必须用
TaskCompletionSource

把事件或回调包装成 Task 的典型写法

比如你有一个老式 API:注册一个

Action<string></string>
回调,操作完成后调用它;你想把它变成
async Task<string></string>
方法:

public Task<string> DoWorkAsync()
{
    var tcs = new TaskCompletionSource<string>();
<pre class="brush:php;toolbar:false;">// 假设这是老接口:void LegacyApi(Action<string> callback)
LegacyApi(result =>
{
    tcs.TrySetResult(result); // 推荐用 TrySet* 系列,避免重复设置异常
});
return tcs.Task;

}

TrySetResult
SetResult
更安全:如果回调被意外触发多次,前者只生效第一次,后者抛
InvalidOperationException
务必处理异常路径:若
LegacyApi
可能失败并传入
Exception
,对应调用
tcs.TrySetException(ex)
不要在回调里捕获异常后吞掉——这会让
await
永远挂起

常见陷阱:同步回调导致死锁或状态错乱

如果老接口是同步执行(比如立即调用回调),而你在 UI 线程或

async
方法里调用它,
TrySetResult
会在当前线程立即触发
Task
完成,可能引发以下问题:

在 WinForms/WPF 中,若未配置
ConfigureAwait(false)
,后续
await
可能尝试切回 UI 线程,但此时线程正忙于执行回调,造成假死
Task
完成后立刻执行
ContinueWith
await
后续代码,若这些代码依赖某些尚未初始化的状态,会出错
解决办法:强制异步化回调体,例如用
Task.Run(() => { ... })
包一层,或在
TrySet*
前加
await Task.Yield()
(仅适用于 async 方法内部)

取消支持:如何让 Task 可被 CancellationToken 触发

TaskCompletionSource
本身不监听
CancellationToken
,需手动绑定:

public Task<string> DoWorkAsync(CancellationToken ct)
{
    var tcs = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
<pre class="brush:php;toolbar:false;">using (ct.Register(() => tcs.TrySetCanceled()))
{
    LegacyApi(result => tcs.TrySetResult(result));
    // 注意:Register 返回的 IDisposable 必须保持引用到回调执行完,
    // 否则可能在回调前就被 GC,导致取消失效
}
return tcs.Task;

}

ct.Register
返回的
IDisposable
必须存活到回调执行完毕,否则取消注册会提前失效
更稳妥的做法是把
IDisposable
存为局部变量,并确保它不会被提前释放(例如不要放进 using 块里就完事)
如果
LegacyApi
本身支持取消,优先用它的原生取消机制,而不是靠
Register
模拟

真正难的不是调用

TrySetResult
,而是判断回调到底在什么时机发生、是否可重入、是否可能失败、是否要响应取消——这些决定了
TaskCompletionSource
的生命周期管理方式。

相关推荐