浅谈C#中的 async await 以及对线程相关知识的复习
C#5.0以后新增了一个语法糖,那就是异步方法async await,之前对线程,进程方面的知识有过较为深入的学习,大概知道这个概念,我的项目中实际用到C#异步编程的场景比较少,就算要用到一般也感觉Task类也基本够用了,所以没有稍微仔细的去研究过这个语法,今天借工作闲暇来梳理一下这个知识点,顺便复习一下线程相关方面的知识,要搞懂这个知识点,需要有一定的基础知识,首先要知道,什么是线程,什么是同步,什么是异步
1. 线程?异步?同步?
什么是线程?规范定义说线程是程序执行流的最小单元,同一时间内只能做一件事情,请说人话,啥意思,举个栗子,假如把人比作一个多线程的软件(此处举例只是为了便于理解,实际中的人脑应该是多进程的),现在这个软件需要做一件叫做吃饭的任务,那么在同一时间内就开启了一个负责吃饭的线程,那这个线程就只能吃饭,不能做其他的事情,如果他想在吃饭的时候“边吃饭边看电视”,就要启用另一个线程,一个线程负责吃饭,一个线程负责看电视。在这里我为啥要把边吃饭边看电视打上引号?因为实际的多线程执行机制中,“边吃饭边看电视”这个两个任务并不是两个任务各在同一时间各自执行,而是快速且交替执行的,也就是两个任务在同一时间只能执行其中一个任务,用一张图来便于理解两个线程如何快速且交替执行,这种线程的执行方式叫做并发执行,而多线程同时执行只是系统带来的一个假像,它在多个单位时间内进行多个线程的切换。因为切换频密而且单位时间非常短暂,所以多线程可被视作同时运行。
下面看一段代码
static void Main(string[] args)
{
//创建 th1 th2两个线程,分别输出字母e和字母w,模拟执行吃饭和看电视两个任务
Thread th1 = new Thread(() => {
for (int i = ; i < ; i++)
{
Console.Write("e");//输出eat
}
});
Thread th2 = new Thread(() => {
for (int i = ; i < ; i++)
{
Console.Write("w");//输出watch
}
});
th1.Start();//启动th1线程
th2.Start();//启动th2线程
Console.ReadKey();
}
我们用for循环输出不同的字母来模拟把两个线程执行的任务碎片化最后看如下执行结果
可以看到输出结果字母e和w是不规则交替输出的,当然你可以使将for循环的i的最大设置的更大一些,来使执行效果更明显,如果你在两个线程内部打上断点,最后运行发现,两个线程在不断的交替执行。
能理解线程之间在同一时间内是并发执行的,那么也就不难理解什么是同步,什么是异步了,同步执行可以理解为代码一行一行按照顺序执行,即完成吃饭的任务后才可以执行看电视,所以一般情况下同步任务,一个线程就可以搞定,异步执行即两个任务的代码代码是交替执行,即吃饭和看电视两个任务是交替运行的,一个线程执行吃饭,一个线程执行看电视,所以不难理解一般涉及到同步异步的东西基本都会跟多线程挂钩。
异步编程的优点:合理的运用异步编程能极大的提升我们的代码运行效率,举一个简单的运用场景,比如界面加载数据渲染时,加载某个模块比较耗时,如果运用同步执行可能会出现加载耗时模块时界面卡死的情况,我们就可以把耗时的模块放在异步任务中,这样就不会影响其他模块的正常加载。ajax应该就是最常见的一个异步编程场景
异步编程是否一定就比同步编程好?
首先代码异步执行CPU需要花费不少的时间在线程的切换上,线程切换也有细微的性能损耗,所以过多地使用多线程反而会导致程序性能的下降。
而且不合理使用线程会造成线程冲突,下面代码我们分别用两个线程去对变量a进行十次++和五次--操作,然后多次运行这个程序,发现每次输出a的结果可能是不一样的,这就是两个线程代码执行顺序的不确定性导致每次执行算出的a的结果都不一样,也就是所谓的异步执行导致线程之间共享数据的冲突
static void Main(string[] args)
{
int a = ;
Thread th1 = new Thread(() => {
for (int i = ; i < ; i++)
{
a++;
}
});
Thread th2 = new Thread(() => {
for (int i = ; i < ; i++)
{
a--;
}
});
th1.Start();
th2.Start();
Console.Write(a);
Console.ReadKey();
}
2. Thread vs Task
上面讲了这么多关于线程,同步,异步的东西,似乎不用 async await语法我们也能实现异步编程,那么 async await语法与传统的ThreadPool.QueueUserWorkItem启动线程(也就是上面的Thread)有什么区别呢? async await的好处又在哪呢?
上面的用代码传统的Thread方法启动线程虽然感觉很简单方便,但是我们我们仔细查看Thread类的构造函数的参数,发现Thread构造函数中所传递的委托是无参数,无返回值的,所以Thread所执行的异步函数的是没有参数,也没有返回结果的,这无疑是个很大的限制
而且对比Thread(线程)和async中Task(任务),二者之间有如下区别
1、任务是架构在线程之上的,也就是说任务最终还是要抛给线程去执行。
2、任务跟线程不是一对一的关系,比如开10个任务并不是说会开10个线程,这一点任务有点类似线程池,但是任务相比线程池有很小的开销和精确的控制,所以Task的控制性和灵活性也是比Thread要好的。
那么在还没有async await语法之前,我们如何解决Thread没有返回值这个问题呢?我们可以运用下面方法,通过委托中的BeginInvoke执行异步代码,然后通过EndInvoke获取异步代码的返回结果,这种异步执行代码的方式可以说是比较灵活了,我们执行异步方法的参数和返回值都是可以变的
static void Main(string[] args)
{
ThreadMessage("Main Thread");
//建立委托
Func<string, string> fuc = new Func<string, string>(Hello);
//异步调用委托,获取计算结果
IAsyncResult result = fuc.BeginInvoke("AsyncWork", null, null);
//完成主线程其他工作
Console.WriteLine("主线程执行常规任务.....");
//等待异步方法完成,调用EndInvoke(IAsyncResult)获取运行结果
string data = fuc.EndInvoke(result);
Console.WriteLine(data); Console.ReadKey();
} static string Hello(string name)
{
ThreadMessage("Async Thread After Two Seconds");
Thread.Sleep(); //模拟异步耗时工作
return "Hello " + name;
} //显示当前线程,输出当前线程的线程ID
static void ThreadMessage(string data)
{
string message = string.Format("{0}\n ThreadId is:{1}",
data, Thread.CurrentThread.ManagedThreadId);
Console.WriteLine(message);
}
3. async await
前面已经讲了相关知识做铺垫。现在来看看async await 关键字,我们来写一个简单的例子,单纯只使用async关键字不使用 await
static void Main(string[] args)
{
DoSomethingAsync();
DoSomethingSync();
Console.ReadKey();
} static void DoSomethingSync()
{
//循环模拟耗时运算
for (int i = ; i < ; i++)
{
Console.Write("*");
}
Console.WriteLine("\nResult: *,我是主线程代码,线程ID:" + Thread.CurrentThread.ManagedThreadId);
} static async void DoSomethingAsync()
{
//循环模拟耗时运算
for (int i = ; i < ; i++)
{
Console.Write("/");
}
Console.WriteLine("\nResult: +,我是async await关键字执行的代码, 线程ID: " + Thread.CurrentThread.ManagedThreadId);
}
运行结果如下,可以看到,DoSomethingAsync()和DoSomethingSync()两个函数在同一个线程上同步执行,所以我们得到一个结论,那就是如果一个异步方法只使用async而不使用await,那么这个所谓的异步方法其实和一个普通的方法没有什么区别,并不会异步执行代码
那我们修改上面的代码,在函数里面加上一个await关键字,会有怎样的结果呢,在使用await之前我们有几个需要注意的点
1:await无法等待void,也就是await后面所等待的函数必须要有返回值
2:所有异步函数返回值必须是void,Task和Task<T>类型,有泛型的存在,(自 C# 7后,貌似还可以指定其他任何返回类型,前提是返回类型包含 GetAwaiter
方法。),所以使用async await时我们也不用担心异步代码无法获取返回值的问题
可以看下面代码分别执行普通函数,Thread启动的函数,和用async await的异步函数
static void Main(string[] args)
{
//async await执行的异步函数
DoSomethingAsync();
//Thread启动的异步函数
DoSomethingAsync2();
//常规同步函数
DoSomethingSync();
Console.ReadKey();
} public static void DoSomethingSync()
{
//循环模拟耗时运算
for (int i = ; i < ; i++)
{
Console.Write("*");
}
Console.WriteLine("\nResult: *,我是主线程代码,线程ID:" + Thread.CurrentThread.ManagedThreadId);
} private static async void DoSomethingAsync()
{
//此处输出执行的任然是主线程代码
Console.WriteLine("\nResult: +,我是只使用async关键字执行的代码, 线程ID: " + Thread.CurrentThread.ManagedThreadId);
//通过await来告诉编译器此处要执行异步代码,所以await后面执行的DoAsync方法是另一线程的异步代码
string msg = await DoAsync();
Console.WriteLine("\nResult: +," + msg);
} private static async Task<string> DoAsync()
{
await Task.Run(() => {
//循环模拟耗时运算
for (int i = ; i < ; i++)
{
Console.Write("+");
}
});
return "我是async await关键字执行的异步代码,线程ID:" + Thread.CurrentThread.ManagedThreadId;
} private static void DoSomethingAsync2()
{
//常规Thread启动新的线程
string msg = string.Empty;
Thread th = new Thread(() => {
//循环模拟耗时运算
for (int i = ; i < ; i++)
{
Console.Write("-");
}
msg = "我是Thread启动的异步代码,线程ID:" + Thread.CurrentThread.ManagedThreadId;
Console.WriteLine("\nResult: -," + msg);
});
th.Start();
}
其实运行代码我们不难发现,所谓的async await异步函数真正实现异步效果的其实还是Task类,async
和 await
关键字本身并不会创建其他线程,而为什么要用async await,而不是单纯的只使用Task,我的个人看法是如果在异步编程中,用async await配合 Task.Run 将占用大量 CPU 的工作移到后台线程,这样会使代码执行效率相对变高,标记的异步方法使用await来指定暂停点这样写也会使代码相对简洁清晰,我们可以清晰的看的哪里是异步执行的代码,哪里是同步执行的代码,也无需防止争用条件,可以理解为async await是微软对我们异步编程的一种规范。
最后再来实战一个async配合使用委托的代码实战,我们来写一段简化版的代码,来模拟类似asp core框架中间件的调用和注册过程,虽然实际的asp core框架的中间件的调用和注册过程是很复杂的,但是大概代码原理也就和下面差不多,多个中间件管道顺序调用并共同维护同一个HttpContext上下文类
delegate Task RequestDelegate(string context); public class Program
{
private static readonly List<Func<RequestDelegate, RequestDelegate>> _middlewares = new List<Func<RequestDelegate, RequestDelegate>>();
public static void Main()
{
//注册中间件
_middlewares.Add(MiddlewareHello);
_middlewares.Add(MiddlewareGoodbye);
_middlewares.Add(MiddlewareOK); RequestDelegate del = async context =>
{
await Task.CompletedTask;
};
_middlewares.Reverse();
//遍历并调用中间件
foreach (var item in _middlewares)
{
del = item(del);
}
del("kangkang");
System.Console.ReadKey();
} static RequestDelegate MiddlewareHello(RequestDelegate del)
{
return async context =>
{
System.Console.WriteLine(" Hello, {0}!", context);
await del(context);
};
} static RequestDelegate MiddlewareGoodbye(RequestDelegate del)
{
return async context =>
{
System.Console.WriteLine(" Goodbye, {0}!", context);
await del(context);
};
} static RequestDelegate MiddlewareOK(RequestDelegate del)
{
return async context =>
{
System.Console.WriteLine(" SayOK, {0}!", context);
};
}
}
浅谈C#中的 async await 以及对线程相关知识的复习的更多相关文章
- 浅谈ES6中的Async函数
转载地址:https://www.cnblogs.com/sghy/p/7987640.html 定义:Async函数是一个异步操作函数,本质上,Async函数是Generator函数的语法糖.asy ...
- 理解C#中的 async await
前言 一个老掉牙的话题,园子里的相关优秀文章已经有很多了,我写这篇文章完全是想以自己的思维方式来谈一谈自己的理解.(PS:文中涉及到了大量反编译源码,需要静下心来细细品味) 从简单开始 为了更容易理解 ...
- 浅谈Java中的equals和==(转)
浅谈Java中的equals和== 在初学Java时,可能会经常碰到下面的代码: 1 String str1 = new String("hello"); 2 String str ...
- [C#] .NET4.0中使用4.5中的 async/await 功能实现异
好东西需要分享 原文出自:http://www.itnose.net/detail/6091186.html 在.NET Framework 4.5中添加了新的异步操作库,但是在.NET Framew ...
- 浅谈Linux中的信号处理机制(二)
首先谢谢 @小尧弟 这位朋友对我昨天夜里写的一篇<浅谈Linux中的信号处理机制(一)>的指正,之前的题目我用的“浅析”一词,给人一种要剖析内核的感觉.本人自知功力不够,尚且不能对着Lin ...
- 浅谈Java中的对象和引用
浅谈Java中的对象和对象引用 在Java中,有一组名词经常一起出现,它们就是“对象和对象引用”,很多朋友在初学Java的时候可能经常会混淆这2个概念,觉得它们是一回事,事实上则不然.今天我们就来一起 ...
- 浅谈Java中的equals和==
浅谈Java中的equals和== 在初学Java时,可能会经常碰到下面的代码: String str1 = new String("hello"); String str2 = ...
- 【TypeScript】如何在TypeScript中使用async/await,让你的代码更像C#。
[TypeScript]如何在TypeScript中使用async/await,让你的代码更像C#. async/await 提到这个东西,大家应该都很熟悉.最出名的可能就是C#中的,但也有其它语言也 ...
- 转【】浅谈sql中的in与not in,exists与not exists的区别_
浅谈sql中的in与not in,exists与not exists的区别 1.in和exists in是把外表和内表作hash连接,而exists是对外表作loop循环,每次loop循环再对内表 ...
随机推荐
- jmeter接口自动化部署jenkins教程
首先,保证本地安装并部署了jenkins,jmeter,xslproc 我搭建的自动化测试框架是jmeter+jenkins+xslproc ---注意:原理是,jmeter自生成的报告jtl文件,通 ...
- 【转载】表单中 Readonly 和 Disabled 的区别
今天写代码,遇到表单提交的问题,某个字段在不同的情况下,要传递不同的值进行赋值,试过一些方法都有些问题,后来请教前端同学,使用 disabled 这个属性终于搞定了问题,查到一篇讲解 readonly ...
- 进程间通信 IPC(Inter-Process Communication)
目录 一.管道 二.FIFO 三.消息队列 四.信号量 五.共享存储 六.网络IPC:套接字 一.管道 管道是进程间通信中最古老的方式,所有UNIX都提供此种通信机制.管道有以下两种局限性: 历史 ...
- 线上问题定位--CPU100%
服务器CPU突然告警,如何定位是哪个服务进程导致CPU过载,哪个线程导致CPU过载,哪段代码导致CPU过载? 步骤一.找到最耗CPU的进程 工具:top 方法: 执行top -d 1 -c,每秒刷新一 ...
- Spring读取配置文件 @Value
最近在学习Spring如何读取配置文件,记录下方便自己也方便别人: 大致分为两类吧,一种的思路是利用Spring的beanFactoryPostProcessor读取配置文件内容到内存中,也就是应用程 ...
- angularjs学习第三天笔记(过滤器第二篇---filter过滤器及其自定义过滤器)
您好,我是一名后端开发工程师,由于工作需要,现在系统的从0开始学习前端js框架之angular,每天把学习的一些心得分享出来,如果有什么说的不对的地方,请多多指正,多多包涵我这个前端菜鸟,欢迎大家的点 ...
- ubuntu 上安装ssh
1. 执行 sudo apt-get update 2. 安装 sudo apt-get install openssh-server 3.查看ssh服务状态 sudo service ssh sta ...
- 浅谈JSONP (vue-jsonp组件 XXXtoken:报错处理)
由于同源策略的存在,特别是前后端两个项目存在的情况下,客户端访问服务端必然存在跨域的情况,而使用jsonp,则不存在这个问题. 主要是因为jsonp是在页面中插入一段js代码,而请求返回的也是一段js ...
- Java基础——Servlet(五)
哈哈哈...学习Servlet学了半个多月,因为中间有比较灰心的时候,有几天是啥都不学了的状态,看了好几部励志的电影.呃~还是得继续吧.本来计划是好好夯实这里的基础,结果在网找到了介绍比较全面的视频, ...
- Java基础——GUI编程(三)
接着前两篇学习笔记,这篇主要介绍布局管理器和对话框两部分内容. 一.布局管理器 先拿一个小例子来引出话题,就按照我们随意的添加两个按钮来说,会产生什么样的效果,看执行结果. import java.a ...