Web常用工具 二维码美化 在线压缩 JavaScript JSON工具 JavaScript AI工具汇总网站 BASE64转换 狂神博客园 图片轮播插件(swiper) W3CHTML W3SCHOOL TypeScript 开源中国 51aspx github codeproject SQLBACKUP 几种排序算法比较 SQL中deny权限 MSDN下载 HttpWebRequest类 HTML5 stackoverflow ASP.NET 页生命周期概述 IIS 5.0 和 6.0 的 ASP.NET 应用程序生命周期概述 [SQL Server]视图sys.sysprocesses brnshop学习 SQL视频 Fiddler帮助文档 Sprint.Net SQLServer牛人博客 codeplex IIS官网 IE11 Developer Jquery源码视频上 Jquery源码视频下 Jquery Bugs jquery.miaov.com 正则表达式 Jquery API 1.9 Service Broker Javascript Object中的方法讲解 Microsoft webcast 微信开发接口 ECMAScript5 Underscore Jquery Bugs SQL存储过程事务嵌套 官网SQL事务锁 2345天气插件 Json数据查看 Javascript设计模式 C++ jquery-plugin-validate 博学谷(传智播客) Swift视频 IOS代码论坛 SWIFT设计模式 操作系统下载 AngularJS VueJS Nuxt Vant-UI官方文档 ASPNETCORE 前端题库 Node.js NPMjs ASPNETCORE 腾讯课堂 SwiftUI SwiftUI疑问解答 ADO.NET SMO 数字化企业网 Unicode码查询 Redis使用文档 .NET 微服务:适用于容器化 .NET 应用程序的体系结构 .NETCore5.0微软官方文档 CSS3.0 在 ASP.NET Core 中配置 Windows 身份验证 Maven使用教程 Maven Repository Thymeleaf Thymeleaf中文CSDN Spring官方文档 SpringBoot SpringData SVG在线设计工具 SVG教程01 SVG教程02 fontawesome图标库 mybatis官网 mybatis-spring中文网 mysql教程 python教程 python的scrapy教程01 python的scrapy教程02 VS开发python xpath教程 腾讯向量数据库教程 JSZip浏览器内存中创建文件与文件夹 axios的使用文档 SheetJS(JS操作excel)的使用文档 极简插件官网(chrome的扩展插件) 金蝶云星空学习成长 常用接口调用 Three.js电子书 D3.js官网 anime.js官网 gsap.js官网 xlsx.js官网 若依框架 若依文档 华为数字人 MDN之JavaScript语法 百度地图API 百度地图API案例 百度地图API使用说明 Nginx中文文档 i18n Animate.css Bootstrap官网 Jquery datatables.net插件 免费SVG C#官网 phet小学初中高中数理化 Pixi.js中文网 webgl教程 unity资源商店国际版 unity官方文档 unity官方着色器Shader unity官方论坛 微信官方文档

huaan011

 

C#中TaskCompletionSource(简称 TCS)学习

今天看到一个 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 等更合适的数据结构。

posted on 2026-08-06 11:09  华安  阅读(7)  评论(0)    收藏  举报

导航