惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

C
Check Point Blog
J
Java Code Geeks
H
Hackread – Cybersecurity News, Data Breaches, AI and More
D
Docker
腾讯CDC
The GitHub Blog
The GitHub Blog
大猫的无限游戏
大猫的无限游戏
Microsoft Security Blog
Microsoft Security Blog
GbyAI
GbyAI
Stack Overflow Blog
Stack Overflow Blog
博客园 - 司徒正美
T
The Blog of Author Tim Ferriss
Vercel News
Vercel News
P
Proofpoint News Feed
雷峰网
雷峰网
博客园_首页
B
Blog RSS Feed
Microsoft Azure Blog
Microsoft Azure Blog
爱范儿
爱范儿
V
V2EX
F
Fortinet All Blogs
酷 壳 – CoolShell
酷 壳 – CoolShell
MyScale Blog
MyScale Blog
S
SegmentFault 最新的问题

博客园 - 华安

C#中Microsoft.Extensions.Caching.Memory 与 System.Runtime.Caching.MemoryCache区别 自适应网格系统:CSS Grid中repeat()、auto-fill与auto-fit的深度解析 CSS3中响应式布局两大神器display:flex和display:grid springBoot中的 pom.xml文件 bulid学习 CSS中元素的display显示方式有多种,隐藏、块级、内联、内联-块级 SQl Server 中的 go 是什么作用 CSS中 display:flex的align-items: stretch; 移动端浏览器(尤其是 iOS Safari)的橡皮筋回弹效果(Overscroll / Bounce Effect) 手机端浏览器上ES6中的Fetch回调执行 window.open没效果 用Flex实现兼容性好的全屏布局 在 VS Code 中使用 C# Dev Kit 和 Unity Tools 调试 Unity 2022 Unity 可编程物件(ScriptableObject) 微信小程序中的 联系客服 最基本的使用方法 wx.requestSubscribeMessage(Object object) 和 wx.requestSubscribeDeviceMessage(Object object) 这两个有什么区别 微信小程序中 wx.hideLoading() 后调用 wx.showToast()的问题 Windows 中启动 Nginx的常用命令 CSS进阶技巧:字体渐变、描边、倒影与渐变色描边全解析 netCore 中各DLL引用了 SkiaSharp.dll的问题 unity中预制体解包 在 Unity 中,Time.timeScale实现游戏暂停加速等 微信小程序中关联微信支付 unity中的 Navigation AI使用 Unity 编辑器 中,快捷键 Ctrl + Shift + F 的功能 unity中按下 F键,让物体聚焦 微信中进入定页面的,判断时通过扫二维码进入的,还是点小程序名称进入的 MYSQL中从JSON字符串中提取指定的值 Unity2022中创建动画 Animation(旧方法) Unity 中区别,public 和 [SerializeField] Unity中 onCollisionEnter2D与OnTriggerEnter2D 区别 asp.netCore中给动态请求路径加客户端缓存
C#中TaskCompletionSource(简称 TCS)学习
华安 · 2026-08-06 · via 博客园 - 华安

今天看到一个 TaskCompletionSource类,感觉超级有用,解决了我一个很困惑的问题。

  1. 概念(高层)
  • TaskCompletionSource<TResult> 是一个“手动完成”的 Task 生产器。它创建并持有一个 Task(通过 .Task 暴露),程序可以在未来的任意时间通过 TrySetResult/TrySetException/TrySetCanceled 来完成该 Task(成功、失败或取消)。
  • 用途:把基于回调/事件/外部信号的异步结果桥接到 async/await(即生产者决定何时完成,而不是由 .NET 运行时自动完成)。
  1. 基本 API(常用)
  • 构造:new TaskCompletionSource<TResult>(); 或 new TaskCompletionSource<TResult>(TaskCreationOptions.RunContinuationsAsynchronously);
  • 读取任务:var task = tcs.Task;
  • 完成成功:tcs.TrySetResult(value) 或 tcs.SetResult(value)
  • 标记异常:tcs.TrySetException(exception) 或 tcs.SetException(exception)
  • 标记取消:tcs.TrySetCanceled() / TrySetCanceled(cancellationToken)
  • 注意:TrySetXxx 更稳健(不会抛异常),SetXxx 在重复完成时会抛 InvalidOperationException。
  1. 最简单示例
var tcs = new TaskCompletionSource<int>();
Task<int> t = tcs.Task;

// 在某个异步操作完成时:
tcs.TrySetResult(42);

// 调用方:
int value = await t; // value == 42
  1. 转换回调到 await(经典用例)
    把事件/回调转换为 Task:
Task<string> WaitForEventAsync()
{
    var tcs = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
    SomeEventSource.OnCompleted += Handler;
    void Handler(object s, string result)
    {
        SomeEventSource.OnCompleted -= Handler;
        tcs.TrySetResult(result);
    }
    return tcs.Task;
}
  1. 异常与取消传播
  • 在消费端(生产者完成 TCS 的代码)应在异常路径调用 TrySetException(ex),在取消路径调用 TrySetCanceled(),否则等待方会一直挂起。
  • 生产者和等待方可以约定用哪种异常或取消方案来表示具体错误/超时。
  1. RunContinuationsAsynchronously 参数(重要)
  • 默认情况下,Task 的 await 续体可能在完成 Task 的线程上同步执行(即当你调用 TrySetResult 时,await 的续体有时会立即执行在当前线程)。这在某些情形会导致死锁或阻塞(尤其与同步上下文/UI 线程混用时)。
  • 建议创建时传入 TaskCreationOptions.RunContinuationsAsynchronously,强制把续体异步调度,从而避免在完成方线程同步执行续体的风险:
    var tcs = new TaskCompletionSource<TResult>(TaskCreationOptions.RunContinuationsAsynchronously);
  1. 取消与超时示例(带 CancellationToken)
var tcs = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
using (cancellationToken.Register(() => tcs.TrySetCanceled(cancellationToken)))
{
    // 启动等待的工作/入队等
    return await tcs.Task; // 若 cancellationToken 被触发,则会抛 TaskCanceledException
}

注意:Register 返回的 IDisposable 要 Dispose(通常用 using 或在 finally 中)。

  1. 超时等待模式(调用方)
var tcs = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
// ...触发工作
var completed = await Task.WhenAny(tcs.Task, Task.Delay(TimeSpan.FromSeconds(10)));
if (completed != tcs.Task) throw new TimeoutException();
int result = await tcs.Task; // 若 Task 被异常完成,这里会抛
  1. 线程安全与行为
  • TaskCompletionSource 的 TrySetXxx/SetXxx 方法都是线程安全的。只允许“单次完成”——首次成功的 TrySet*** 将生效,后续尝试返回 false(TrySet)或抛异常(Set)。
  • 因为只能完成一次,通常一条工作链只创建一个 TCS。
  1. 常见陷阱
  • 不用 RunContinuationsAsynchronously 可能导致续体在 Set 的线程上执行,引起性能或死锁(特别是 UI/同步上下文场景)。
  • 忘记在异常路径设置 TrySetException,会导致等待方永远挂起(内存/资源泄漏)。
  • 忘记处理或 Dispose CancellationToken 注册,会造成内存泄漏。
  • 将 TCS 误用为“多次完成”的信号(TCS 只能完成一次)。
  • 同步等待 Task.Result/Wait() (特别在 UI/ASP.NET 同步上下文)容易造成死锁;尽量使用 await。
  1. 与其它同步原语对比
  • ManualResetEventSlim / TaskCompletionSource:MRE 是阻塞等待(同步),而 TCS 是异步/可 await 的。
  • Task.Run:用于把工作放在线程池执行,不用于“等待某外部事件由其他线程完成”的桥接。TCS 用于表示未来由外部决定完成的单次结果。
  • Channels / IAsyncEnumerable:用于持续的多项异步流,若需要多次、流式结果,不应用单个 TCS。
  1. 使用场景建议
  • 将基于回调/事件的 API 包装为 async/await。
  • 在生产者-消费者场景中,为每个入队项提供一个 TCS,让调用方能够“入队并等待”该项被处理。
  • 在测试、RPC、等待一次性信号或异步完成通知时使用。
  1. 调试技巧
  • 如果 await 某个 TCS.Task 永远不返回,检查消费端是否在所有路径(成功/异常/取消)都设置了 TrySetXxx。
  • 可以把 TrySetException 的异常做日志记录以便追踪失败原因。
  • 使用 TaskCreationOptions.RunContinuationsAsynchronously 后,续体的堆栈信息更容易与异步上下文分离,调试更清晰。
  1. 示例:用 TCS 实现一次性事件等待(独立)
class OneShotSignal
{
    private TaskCompletionSource<bool> _tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
    public Task WaitAsync() => _tcs.Task;
    public void Signal() => _tcs.TrySetResult(true);
    public void Fail(Exception ex) => _tcs.TrySetException(ex);
    public void Cancel() => _tcs.TrySetCanceled();
}

总结(要点)

  • TaskCompletionSource 非常强大:能把任意“未来会发生”的事件表示为 Task,方便 await。
  • 永远优先使用 TrySetXXX,并在创建时考虑 TaskCreationOptions.RunContinuationsAsynchronously。
  • 关注取消、异常处理以及注册的释放,避免资源泄漏或死等。
  • 当需要表示多次或流式结果时,使用 Channel / IAsyncEnumerable 等更合适的数据结构。