在与同事讨论async/await内部实现的时候,突然想到Task.Yeild()这个函数,为什么呢,了解一点C#async/await内部机制的都知道,在await一个异步任务(函数)的时候,它会先判断该Task是否已经完成,如果已经完成,则继续执行下去,不会返回到调用方,原因是尽量避免线程切换,因为await后面部分的代码很可能是另一个不同的线程执行,而Task.Yeild()则可以强制回到调用方,或者说主动让出执行权,给其他Task执行的机会,可以把Task理解为协程,Task.Yeild()和Thread.sleep(0)有点相同。

为了证明我的结论成立,请看代码:

 public static async Task Test1()
{
await Task.CompletedTask;
Thread.Sleep();
Console.WriteLine("Test1任务完成");
}
public static async Task Test2()
{
await Task.Delay();
Thread.Sleep();
Console.WriteLine("Test2任务完成");
}
public static async Task Test3()
{
await Task.Yield();
Thread.Sleep();
Console.WriteLine("Test3任务完成");
}
static void Main(string[] args)
{
Console.WriteLine(DateTime.Now);
_ = Test1();
Console.WriteLine(DateTime.Now);
Console.ReadLine();
}

按照开头的理论,Test1()异步函数由于await了一个已经完成的任务,所以会继续往下执行,阻塞1秒钟,然后回到调用方,打印的时间之差会相隔一秒。

Test2()异步函数由于await了一个未完成的任务(1ms对于CPU来说是很长的了),所以会返回调用方,然后打印相同的时间,一秒钟之后会打印执行完毕。

Test3()调用了Task.Yeild()函数,主动让出执行权,所以会直接返回调用方,然后打印相同的时间,一秒之后会打印执行完毕。

可以看到,开头的结论是正确的。那么,有什么意义呢?Yeild的意思在这里其实就是退让,让出的意思,让出什么呢?就是让出执行权,这与Thread.sleep(0)让出CPU执行权给其他线程(前提是有其他线程竞争)有机会执行是一个道理。

请看我的例子:

 public static async Task OP1()
{
while (true)
{
await Task.Yield();//这里会捕捉同步上下文,由于是控制台程序,没有同步上下文,所以默认的线程池任务调度器变成同步上下文
//也就是说后面的代码将会在线程池上执行,由于线程池工作线程数量设置为1,所以必须主动让出执行权,让其他的
//任务有执行的机会
Console.WriteLine("OP1在执行");
Thread.Sleep();//模拟一些需要占用CPU的操作
}
}
public static async Task OP2()
{
while (true)
{
await Task.Yield();
Console.WriteLine("OP2在执行");
Thread.Sleep();
}
}
static async Task Main(string[] args)
{
ThreadPool.SetMinThreads(, );
ThreadPool.SetMaxThreads(, );
//Task.Run()方法默认使用线程池任务调度器执行任务,由于主线程不是线程池线程,所以使用Task.Run()
var t = Task.Run(async () =>
{
var t1 = OP1();
var t2 = OP2();
await Task.WhenAll(t1, t2);
});
await t;
Console.ReadLine();
}

可以看出OP1()和OP2()两个协程(Task)互相争用一个线程(用户模式下的CPU),如果不主动让出执行权,另一个协程(Task)将不会有机会执行。

例如:

 public static async Task OP2()
{
while (true)
{
await Task.CompletedTask;//或者是直接去掉
Console.WriteLine($"OP2在执行 {DateTime.Now}");
Thread.Sleep();
}
}

这样OP1()将永远不会有机会执行。

C#中关于Task.Yeild()的探究的更多相关文章

  1. (转).NET 4.5中使用Task.Run和Parallel.For()实现的C# Winform多线程任务及跨线程更新UI控件综合实例

    http://2sharings.com/2014/net-4-5-task-run-parallel-for-winform-cross-multiple-threads-update-ui-dem ...

  2. Ext.Net中的Task控件的使用

    在用到Ext.Net中的Task控件的时候,写了一下基本的使用方法: 控件是在TaskManager里面的Tasks下面的Task 此控件的常用属性有,TaskID.Interval(设置间隔时间). ...

  3. SSIS中Sql Task 获取系统变量

    原文:SSIS中Sql Task 获取系统变量 执行 SQL 任务使用不同的连接类型时,SQL 命令的语法使用不同的参数标记.例如,ADO.NET 连接管理器类型要求 SQL 命令使用格式为 @var ...

  4. Linux中的task,process, thread 简介

    本文的主要目的是介绍在Linux内核中,task,process, thread这3个名字之间的区别和联系.并且和WINDOWS中的相应观念进行比较.如果你已经很清楚了,那么就不用往下看了. LINU ...

  5. Proxmox VE中出现TASK ERROR: command 'apt-get update' failed: exit code 100的解决方法

    问题描述: 出现这个错误一般在WEB或者在Proxmox VE的服务器上面能看到日志: PVE中出现TASK ERROR: command 'apt-get update' failed: exit ...

  6. Android中的task和stack

    今天在重新理了一遍intent的过程中发现task是一个神奇的东西,而它又和stack有着很深的联系.task顾名思义是一个任务,但是这个任务可不一定只是来自一个app,比如我用微信来发一张图片,那么 ...

  7. SV中的task和function

    SV中class的properties和methods默认都是public的,但是可以声明为local和protected. 一个properties声明为local类型的,则只在该class中的me ...

  8. Java并发包中线程池ThreadPoolExecutor原理探究

    一.线程池简介 线程池的使用主要是解决两个问题:①当执行大量异步任务的时候线程池能够提供更好的性能,在不使用线程池时候,每当需要执行异步任务的时候直接new一个线程来运行的话,线程的创建和销毁都是需要 ...

  9. swoole中使用task进程异步的处理耗时任务

    我们知道,swoole中有两大进程,分别是 master 主进程和 manager 管理进程. 其中 master 主进程中会有一个主 reactor 线程和多个 reactor 线程,主要的作用就是 ...

随机推荐

  1. JMeter接口压测和性能监测

    JMeter接口压力测试总结 一.安装JMeter 1.     在客户端机器上安装JMeter压测工具,我这里安装的版本是apache-jmeter-5.2.1,由于JMeter是JAVA语言开发的 ...

  2. django——bbs

    今日内容概要 bbs是一个前后端不分离的全栈项目,前端和后端都需要我们自己一步步的完成 表创建及同步 注册功能 forms组件 用户头像前端实时展示 ajax 登陆功能 自己实现图片验证码 ajax ...

  3. C和C++中static的比较

    using namespace std; class A{ private: static int a;//由static修饰的变量仅仅是一个声明,不能在此处进行初始化,需要在类的外部初始化. voi ...

  4. 使用PyQtGraph绘制数据滚动图形(3)

    import pyqtgraph as pg import numpy as np from pyqtgraph.Qt import QtGui, QtCore app = pg.QtGui.QApp ...

  5. 使用Docker搭建Nextcloud SSL站点

    1.启动mariadb docker run -d \ --name mysql \ -e MYSQL_ROOT_PASSWORD=<你的mysql密码> \ -p 13306:3306 ...

  6. Centos6,Centos7防火墙设置与端口开放的方法

    Centos升级到7之后,内置的防火墙已经从iptables变成了firewalld.端口的开启还是要从两种情况来说明的,即iptables和firewalld. 一.iptables 1.打开/关闭 ...

  7. (数据科学学习手札87)利用adjustText解决matplotlib文字标签遮挡问题

    本文示例代码.数据已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 在进行数据可视化时我们常常需要在可视化作品 ...

  8. CentOS 7设置静态IP并修改DNS

    1. 设置静态IP 首先需要确定网线插在服务器的哪一个网络接口上,接口旁边一般都有写.我这边是插在1号接口上的. 然后修改网络配置文件,文件位于 /etc/sysconfig/network-scri ...

  9. Springboot打包放到Tomcat中报错 One or more listener fail to start

    1.问题: Springboot项目直接启动不报错,打war包放到外部容器Tomcat.东方通上,在@Weblistener注解的监听器类中报错 One or more listener fail t ...

  10. 小白的mapbox学习之路-显示地图

    刚接触mapbox,只是简单记下自己的学习之路,如有错误,欢迎大神指正 1-头部引入链接 2-body中定义一个div块,用来显示地图 3-在script中创建一个map对象,并设置相关参数 mapb ...