终于明白了 C# 中 Task.Yield 的用途

最近在阅读 .NET Threadpool starvation, and how queuing makes it worse 这篇博文时发现文中代码中的一种 Task 用法之前从未见过,在网上看了一些资料后也是云里雾里不知其解,很是困扰。今天在程序员节的大好日子里终于想通了,于是写下这篇随笔分享给大家,也过过专心写博客的瘾。

这种从未见过的用法就是下面代码中的 await Task.Yield()

static async Task Process()
{
    await Task.Yield();

    var tcs = new TaskCompletionSource<bool>();

    Task.Run(() =>
    {
        Thread.Sleep(1000);
        tcs.SetResult(true);
    });

    tcs.Task.Wait();
}

(注:上面的代码不是示例,只是因为这段代码而初遇 await Task.Yield)

Task.Yield 简单来说就是创建时就已经完成的 Task ,或者说执行时间为0的 Task ,或者说是空任务,也就是在创建时就将 Task 的 IsCompeted 值设置为0。

那 await 一个空任务会怎样?我们知道在 await 时会释放当前线程,等所 await 的 Task 完成时会从线程池中申请新的线程继续执行 await 之后的代码,这本来是为了解决异步操作(比如IO操作)霸占线程实际却用不到线程的问题,而 Task.Yield 却产生了一个不仅没有异步操作而且什么也不干的 Task ,不是吃饱了撑着吗?

今天吃晚饭的时候终于想明白了——吃饱了没有撑。Task.Yield 产生的空任务仅仅是为 await 做嫁衣,而真正的图谋是借助 await 实现线程的切换,让 await 之后的操作重新排队从线程池中申请线程继续执行。这样做有什么好处呢?线程是非常非常宝贵的资源,千金难买一线程,而且有优先级,提高线程利用率的重要手段之一就是及时将线程分配给最需要的地方,而最奢侈的之一是让一个优先级低执行时间长的操作一直占用着一个线程,await Task.Yield 可以让你巧妙地借助 await 的线程切换能力,将不太重要的比较耗时的操作放在新的线程(重新排队从线程池中申请到的线程)中执行。打个比方,很多人排队在外婆家就餐,你来的时候比较巧,正好有位置,但你本来就不着急肚子也不太饿准备慢慢吃慢慢聊,而排队的人当中有些人很饿很着急吃完还有事,这时你如果先点几个招牌菜解解馋,然后将座位让出来,重新排队,并且排队的人当中像你这样的都这么做,那些排队中心急如焚的人真是是幸福感爆棚,外婆家的老板也笑弯了腰。你让出座位重新排队的爱心行为就是 await Task.Yield()

祝大家程序员节快乐!

补充 - 后来发现的相关链接:

posted @   dudu  阅读(22545)  评论(44编辑  收藏  举报
编辑推荐:
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
· C++代码改造为UTF-8编码问题的总结
· DeepSeek 解答了困扰我五年的技术问题
· 为什么说在企业级应用开发中,后端往往是效率杀手?
阅读排行:
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 推荐几款开源且免费的 .NET MAUI 组件库
· 实操Deepseek接入个人知识库
· 易语言 —— 开山篇
· Trae初体验
历史上的今天:
2013-10-24 用了快1年的MacBook Pro遇到的硬件问题
2005-10-24 网站运行情况汇报
2004-10-24 CNDotText 1.0 Alpha版 提供下载
2004-10-24 博客园.Text技术支持移至PSP技术支持中心
点击右上角即可分享
微信分享提示