一、简介sql
再说Windows的异步I/O操做前,先聊聊一些题外话,能帮助咱们更好的理解异步I/O操做,常规的Web程序,当用户发起一次请求,当请求经过管道到达客户端的这个过程,会唤起一个线程池线程(后台线程),处理咱们的业务代码,即全部的用户请求是经过异步的方式发起的,这个过程,.Net Framework会自动进行,即便咱们没有显示的经过代码来实现这个过程.因此这个过程明显是存在性能瓶颈的,假设如今有一个4核服务器,意味这该服务器同时只能处理4个用户请求(超理想状况下,通常不可能),可是这个时候来了10000个用户请求(并发执行)的状况下,那么意味者大量线程会堆积起来,等待着前面的线程执行完毕,同时进行频繁的上下文切换,这个时候你会发现CPU会爆表.数据库
上面只是一个例子,再说一个数据库的例子,如今须要向数据库插入20000条记录,分为三个版本去实现,第一个版本是单个线程同步插入,第二个版本多线程同步插入(Parallel),第三个版本多线程异步插入,来比较下性能和CPU利用零及使用状况.api
(1)、单线程同步版本服务器
这个场景是只有一个用户请求进来,进行20000次的数据库插入操做,这个版本不会产生线程堆积,由于全部的插入操做都只由主线程完成.多线程
private static readonly string ConnectionStrings; static Program() { //配置数据库链接 ConnectionStrings = ConfigurationManager.ConnectionStrings["connStr"].ConnectionString; } static void Main(string[] args) { var stop = Stopwatch.StartNew(); InstertSync(); stop.Stop(); Console.WriteLine($"同步执行20000次插入操做,耗时:{stop.ElapsedMilliseconds/1000}秒"); Console.ReadKey(); } private static void InstertSync() { var totalCount = 0; var failCount = 0; //这里以同步方式执行数据库操做,注这里只有一个线程执行全部的数据库插入操做 for (int i = 0; i <= 20000; i++) { var conn = new SqlConnection(ConnectionStrings); conn.Open(); try { //模拟数据库耗时操做 var sql = "insert into [dbo].[User]([Amount]) values (@Amount)"; var command = new SqlCommand(sql, conn); command.Parameters.Add( new SqlParameter("@Amount", i) ); //这里线程会等待这一段时间,等待数据库返回结果,并继续执行下面的代码 var result = command.ExecuteNonQuery(); if (result == 1) { totalCount+=1; } else { failCount+=1; } Console.WriteLine($"成功插入{totalCount}条记录,插入失败{failCount}条记录"); } catch (Exception ex) { throw ex; } finally { conn.Close(); conn.Dispose(); } } }
再看看数据库的批请求数数据并发
大概稳定在300次左右每秒异步
(2)、多线程同步async
这个场景是大多数没有使用Async Await模型的Web应用程序(Parallel表明同时有多个用户请求进来),同时数据库也使用的是同步Api,这个时候以同步的方式发起数据库请求,每一个线程会等待不肯定的时间,等待数据库返回结果,同时另外一个线程开启,也会等待数据库返回结果,这样用户请求一多,就会产生大量的线程堆积,形成大量的内存浪费,并且当数据库开始响应线程时,线程会被唤醒,所有开始执行,这时候CPU又会开始繁忙的执行.性能
private static readonly string ConnectionStrings; static Program() { //配置数据库链接 ConnectionStrings = ConfigurationManager.ConnectionStrings["connStr"].ConnectionString; } static void Main(string[] args) { InsertAsync(); Console.ReadKey(); } private static void InsertAsync() { var stop = Stopwatch.StartNew(); var totalCount = 0; var failCount = 0; var res = Parallel.For(0, 20000, i => { var conn = new SqlConnection(ConnectionStrings); conn.Open(); try { //模拟数据库耗时操做 var sql = "insert into [dbo].[User]([Amount]) values (@Amount)"; var command = new SqlCommand(sql, conn); command.Parameters.Add( new SqlParameter("@Amount", i) ); var result = command.ExecuteNonQuery(); if (result == 1) { Interlocked.Add(ref totalCount, 1); } else { Interlocked.Add(ref failCount, 1); } Console.WriteLine($"成功插入{totalCount}条记录,插入失败{failCount}条记录"); } catch (Exception ex) { throw ex; } finally { conn.Close(); conn.Dispose(); } }); if (res.IsCompleted) { stop.Stop(); Console.WriteLine($"同步执行20000次插入操做,耗时:{stop.ElapsedMilliseconds / 1000}秒"); } }
去除Interlocked稍稍快一些.明显能够发如今多线程环境下,使用同步的数据库操做api,效率显著降低.CPU的利用率也很低,同时跑了不少操做线程,但数据库使用同步Api,只能响应一个线程,其他的都须要排队.spa
再看看数据库批请求数
只能稳定在130次左右,说明多线程环境下,使用同步数据库操做,阻碍了请求的提交速度.我的理解.
(3)、多线程异步
这个场景用户使用基于Async Await模型的Web程序,且使用数据库的异步Api
private static readonly string ConnectionStrings; static Program() { //配置数据库链接 ConnectionStrings = ConfigurationManager.ConnectionStrings["connStr"].ConnectionString; } static void Main(string[] args) { InsertAsync(); Console.ReadKey(); } private static void InsertAsync() { var stop = Stopwatch.StartNew(); var totalCount = 0; var failCount = 0; var res = Parallel.For(0, 20000,async i => { var conn = new SqlConnection(ConnectionStrings); conn.Open(); try { //模拟数据库耗时操做 var sql = "insert into [dbo].[User]([Amount]) values (@Amount)"; var command = new SqlCommand(sql, conn); command.Parameters.Add( new SqlParameter("@Amount", i) ); var result =await command.ExecuteNonQueryAsync(); if (result == 1) { Interlocked.Add(ref totalCount, 1); } else { Interlocked.Add(ref failCount, 1); } Console.WriteLine($"成功插入"); } catch (Exception ex) { throw ex; } finally { conn.Close(); conn.Dispose(); } }); if (res.IsCompleted) { stop.Stop(); Console.WriteLine($"同步执行20000次插入操做,耗时:{stop.ElapsedMilliseconds / 1000}秒"); } }
能够发现这个模式插入效率很是之高.可是它的插入是无序的,由于Parallel执行线程的顺序是无序的.CPU的利用率也是极高的.
再看看数据库批请求数
直线飙升>1000次的请求提交,说明使用异步Api数据库每秒接收的请求数,远大于同步方式,也是使用异步Api如此之快的缘由.