.net异步性能测试(包括ASP.NET MVC WebAPI异步方法)
很久没有写博客了,今年做的产品公司这两天刚刚开了发布会,稍微清闲下来,想想我们做的产品还有没有性能优化空间,于是想到了.Net的异步可以优化性能,但到底能够提升多大的比例呢?恰好有一个朋友正在做各种语言的异步性能测试(有关异步和同步的问题,请参考客《AIO与BIO接口性能对比》),于是我今天写了一个C#的测试程序。
首先,建一个 ASP.NET MVC WebAPI项目,在默认的控制器 values里面,增加两个方法:
// GET api/values?sleepTime=10
[HttpGet]
public async Task<string> ExecuteAIO(int sleepTime)
{
await Task.Delay(sleepTime);
return "Hello world,"+ sleepTime;
} [HttpGet]
// GET api/values?sleepTime2=10
public string ExecuteBIO(int sleepTime2)
{
System.Threading.Thread.Sleep(sleepTime2);
return "Hello world," + sleepTime2;
}
然后,建立一个控制台程序,来测试这个web API:
class Program
{
static void Main(string[] args)
{
Console.WriteLine("按任意键开始测试 WebAPI:http://localhost:62219/api/values?sleepTime={int}");
Console.Write("请输入线程数:");
int threadNum = ;
int.TryParse(Console.ReadLine(), out threadNum);
while (Test(threadNum)) ; Console.ReadLine();
Console.ReadLine();
} private static bool Test(int TaskNumber)
{
Console.Write("请输入此API方法的睡眠时间(毫秒),输入非数字内容退出:");
string input = Console.ReadLine();
int SleepTime = ;
if (!int.TryParse(input, out SleepTime))
return false;
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:62219/");
var result = client.GetStringAsync("api/values?sleepTime=" + input).Result;
Console.WriteLine("Result:{0}", result);
//int TaskNumber = 1000; Console.WriteLine("{0}次 BIO(同步)测试(睡眠{1} 毫秒):", TaskNumber, SleepTime);
System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); sw.Start();
Task[] taskArr = new Task[TaskNumber];
for (int i = ; i < TaskNumber; i++)
{
Task task = client.GetStringAsync("api/values?sleepTime2=" + SleepTime);
taskArr[i] = task; }
Task.WaitAll(taskArr);
sw.Stop();
double useTime1 = sw.Elapsed.TotalSeconds;
Console.WriteLine("耗时(秒):{0},QPS:{1,10:f2}", useTime1, TaskNumber/useTime1);
sw.Reset(); Console.WriteLine("{0}次 AIO(异步)测试(睡眠{1} 毫秒):", TaskNumber, SleepTime);
sw.Start();
for (int i = ; i < TaskNumber; i++)
{
Task task = client.GetStringAsync("api/values?sleepTime=" + SleepTime);
taskArr[i] = task;
}
Task.WaitAll(taskArr);
sw.Stop();
double useTime2 = sw.Elapsed.TotalSeconds;
Console.WriteLine("耗时(秒):{0},QPS:{1,10:f2}", useTime2, TaskNumber / useTime2);
return true;
}
}
其实主要是下面几行代码:
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:62219/");
var result = client.GetStringAsync("api/values?sleepTime=" + input).Result;
注意,你可能需要使用Nuget添加下面这个包:
Microsoft.AspNet.WebApi.Client
最后,运行这个测试,结果如下:
按任意键开始测试 WebAPI:http://localhost:62219/api/values?sleepTime={int}
请输入线程数:1000
请输入此API方法的睡眠时间(毫秒),输入非数字内容退出:10
Result:"Hello world,10"
1000次 BIO(同步)测试(睡眠10 毫秒):
耗时(秒):1.2860545,QPS: 777.57
1000次 AIO(异步)测试(睡眠10 毫秒):
耗时(秒):0.4895946,QPS: 2042.51
请输入此API方法的睡眠时间(毫秒),输入非数字内容退出:100
Result:"Hello world,100"
1000次 BIO(同步)测试(睡眠100 毫秒):
耗时(秒):8.2769307,QPS: 120.82
1000次 AIO(异步)测试(睡眠100 毫秒):
耗时(秒):0.5435111,QPS: 1839.89
本来想尝试测试10000个线程,但报错了。
上面的测试结果,QPS并不高,但由于使用的是IISExpress,不同的Web服务器软件性能不相同,所以还得对比下进程内QPS结果,于是新建一个控制台程序,代码如下:
class Program
{
static void Main(string[] args)
{
Console.WriteLine("按任意键开始测试 ");
Console.Write("请输入线程数:");
int threadNum = ;
int.TryParse(Console.ReadLine(), out threadNum);
while (Test(threadNum)) ; Console.ReadLine();
Console.ReadLine();
} private static bool Test(int TaskNumber)
{
Console.Write("请输入此API方法的睡眠时间(毫秒),输入非数字内容退出:");
string input = Console.ReadLine();
int SleepTime = ;
if (!int.TryParse(input, out SleepTime))
return false; var result = ExecuteAIO(SleepTime).Result;
Console.WriteLine("Result:{0}", result);
//int TaskNumber = 1000; Console.WriteLine("{0}次 BIO(同步)测试(睡眠{1} 毫秒):", TaskNumber, SleepTime);
System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); sw.Start();
Task[] taskArr = new Task[TaskNumber];
for (int i = ; i < TaskNumber; i++)
{
Task task = Task.Run<string>(()=> ExecuteBIO(SleepTime));
taskArr[i] = task; }
Task.WaitAll(taskArr);
sw.Stop();
double useTime1 = sw.Elapsed.TotalSeconds;
Console.WriteLine("耗时(秒):{0},QPS:{1,10:f2}", useTime1, TaskNumber / useTime1);
sw.Reset(); Console.WriteLine("{0}次 AIO(异步)测试(睡眠{1} 毫秒):", TaskNumber, SleepTime);
sw.Start();
for (int i = ; i < TaskNumber; i++)
{
Task task = ExecuteAIO(SleepTime);
taskArr[i] = task;
}
Task.WaitAll(taskArr);
sw.Stop();
double useTime2 = sw.Elapsed.TotalSeconds;
Console.WriteLine("耗时(秒):{0},QPS:{1,10:f2}", useTime2, TaskNumber / useTime2);
return true;
} public static async Task<string> ExecuteAIO(int sleepTime)
{
await Task.Delay(sleepTime);
return "Hello world," + sleepTime;
} public static string ExecuteBIO(int sleepTime2)
{
System.Threading.Thread.Sleep(sleepTime2);
//不能在非异步方法里面使用 Task.Delay,否则可能死锁
//Task.Delay(sleepTime2).Wait();
return "Hello world," + sleepTime2;
}
}
注意,关键代码只有下面两个方法:
public static async Task<string> ExecuteAIO(int sleepTime)
{
await Task.Delay(sleepTime);
return "Hello world," + sleepTime;
} public static string ExecuteBIO(int sleepTime2)
{
System.Threading.Thread.Sleep(sleepTime2);
//不能在非异步方法里面使用 Task.Delay,否则可能死锁
//Task.Delay(sleepTime2).Wait();
return "Hello world," + sleepTime2;
}
这两个方法跟WebAPI的测试方法代码是一样的,但是调用代码稍微不同:
同步调用:
Task[] taskArr = new Task[TaskNumber];
for (int i = ; i < TaskNumber; i++)
{
Task task = Task.Run<string>(()=> ExecuteBIO(SleepTime));
taskArr[i] = task; }
Task.WaitAll(taskArr);
异步调用:
for (int i = ; i < TaskNumber; i++)
{
Task task = ExecuteAIO(SleepTime);
taskArr[i] = task;
}
Task.WaitAll(taskArr);
可见,这里测试的时候,同步和异步调用,客户端代码都是使用的多线程,主要的区别就是异步方法使用了 async/await 语句。
下面是非Web的进程内异步多线程和同步多线程的结果:
请输入线程数:1000
请输入此API方法的睡眠时间(毫秒),输入非数字内容退出:10
Result:Hello world,10
1000次 BIO(同步)测试(睡眠10 毫秒):
耗时(秒):1.3031966,QPS: 767.34
1000次 AIO(异步)测试(睡眠10 毫秒):
耗时(秒):0.026441,QPS: 37820.05
请输入此API方法的睡眠时间(毫秒),输入非数字内容退出:100
Result:Hello world,100
1000次 BIO(同步)测试(睡眠100 毫秒):
耗时(秒):9.8502858,QPS: 101.52
1000次 AIO(异步)测试(睡眠100 毫秒):
耗时(秒):0.1149469,QPS: 8699.67 请输入线程数:10000
请输入此API方法的睡眠时间(毫秒),输入非数字内容退出:10
Result:Hello world,10
10000次 BIO(同步)测试(睡眠10 毫秒):
耗时(秒):7.7966125,QPS: 1282.61
10000次 AIO(异步)测试(睡眠10 毫秒):
耗时(秒):0.083922,QPS: 119158.27
请输入此API方法的睡眠时间(毫秒),输入非数字内容退出:100
Result:Hello world,100
10000次 BIO(同步)测试(睡眠100 毫秒):
耗时(秒):34.3646036,QPS: 291.00
10000次 AIO(异步)测试(睡眠100 毫秒):
耗时(秒):0.1721833,QPS: 58077.64
结果表示,.NET程序开启10000个任务(不是10000个原生线程,需要考虑线程池线程),异步方法的QPS超过了10万,而同步方法只有1000多点,性能差距还是很大的。
注:以上测试结果的测试环境是
Intel i7-4790K CPU,4核8线程,内存 16GB,Win10 企业版
总结:
不论是普通程序还是Web程序,使用异步多线程,可以极大的提高系统的吞吐量。
后记:
感谢网友“双鱼座” 的提示,我用信号量和都用线程Sleep的方式,对同步和异步方法进行了测试,结果如他所说,TPL异步方式,开销很大,下面是测试数据:
使用 semaphoreSlim 的情况: 请输入线程数:1000
请输入此API方法的睡眠时间(毫秒),输入非数字内容退出:10
Result:Hello world,10
1000次 BIO(同步)测试(睡眠10 毫秒):
耗时(秒):1.2486964,QPS: 800.84
1000次 AIO(异步)测试(睡眠10 毫秒):
耗时(秒):10.5259443,QPS: 95.00
请输入此API方法的睡眠时间(毫秒),输入非数字内容退出:100
Result:Hello world,100
1000次 BIO(同步)测试(睡眠100 毫秒):
耗时(秒):12.2754003,QPS: 81.46
1000次 AIO(异步)测试(睡眠100 毫秒):
耗时(秒):100.5308431,QPS: 9.95
请输入此API方法的睡眠时间(毫秒),输入非数字内容退出:1000
Result:Hello world,1000
1000次 BIO(同步)测试(睡眠1000 毫秒):
耗时(秒):54.0055828,QPS: 18.52
1000次 AIO(异步)测试(睡眠1000 毫秒):
耗时(秒):1000.4749124,QPS: 1.00
使用线程 Sleep的代码改造:
public static async Task<string> ExecuteAIO(int sleepTime)
{
//await Task.Delay(sleepTime);
//return "Hello world," + sleepTime;
//await Task.Delay(sleepTime);
//semaphoreSlim.Wait(sleepTime);
System.Threading.Thread.Sleep(sleepTime);
return await Task.FromResult("Hello world," + sleepTime);
} public static string ExecuteBIO(int sleepTime2)
{
System.Threading.Thread.Sleep(sleepTime2);
//semaphoreSlim.Wait(sleepTime2);
//不能在非异步方法里面使用 Task.Delay,否则可能死锁
//Task.Delay(sleepTime2).Wait();
return "Hello world," + sleepTime2;
}
运行结果如下:
请输入线程数:1000
请输入此API方法的睡眠时间(毫秒),输入非数字内容退出:10
Result:Hello world,10
1000次 BIO(同步)测试(睡眠10 毫秒):
耗时(秒):1.3099217,QPS: 763.40
1000次 AIO(异步)测试(睡眠10 毫秒):
耗时(秒):10.9869045,QPS: 91.02
请输入此API方法的睡眠时间(毫秒),输入非数字内容退出:100
Result:Hello world,100
1000次 BIO(同步)测试(睡眠100 毫秒):
耗时(秒):8.5861461,QPS: 116.47
1000次 AIO(异步)测试(睡眠100 毫秒):
耗时(秒):100.9829406,QPS: 9.90
请输入此API方法的睡眠时间(毫秒),输入非数字内容退出:1000
Result:Hello world,1000
1000次 BIO(同步)测试(睡眠1000 毫秒):
耗时(秒):27.0158904,QPS: 37.02
1000次 AIO(异步)测试(睡眠1000 毫秒):
在每次睡眠1秒的异步方法测试中,很久都没有出来结果,不用考虑,QPS肯定低于一秒了。
经验教训:
在异步方法中,不要使用 Thread.Sleep;在同步方法中,不要使用Task.Delay ,否则可能出现线程死锁,结果难出来。
.net异步性能测试(包括ASP.NET MVC WebAPI异步方法)的更多相关文章
- 让Asp.net mvc WebAPI 支持OData协议进行分页查询操作
这是我在用Asp.net mvc WebAPI 支持 OData协议 做分页查询服务时的 个人拙笔. 代码已经开发到oschina上.有兴趣的朋友可以看看,欢迎大家指出不足之处. 看过了园子里的几篇关 ...
- 案例:1 Ionic Framework+AngularJS+ASP.NET MVC WebApi Jsonp 移动开发
落叶的庭院扫的一干二净之后,还要轻轻把树摇一下,抖落几片叶子,这才是Wabi Sabi的境界. 介绍:Ionic是移动框架,angularjs这就不用说了,ASP.Net MVC WebApi提供数据 ...
- ASP.NET MVC WebApi 返回数据类型序列化控制(json,xml) 用javascript在客户端删除某一个cookie键值对 input点击链接另一个页面,各种操作。 C# 往线程里传参数的方法总结 TCP/IP 协议 用C#+Selenium+ChromeDriver 生成我的咕咚跑步路线地图 (转)值得学习百度开源70+项目
ASP.NET MVC WebApi 返回数据类型序列化控制(json,xml) 我们都知道在使用WebApi的时候Controller会自动将Action的返回值自动进行各种序列化处理(序列化为 ...
- [渣翻译] 在ASP.NET MVC WebAPI项目中使用 AngularJS
原文地址http://blog.technovert.com/2013/12/setting-up-angularjs-for-asp-net-mvc-n-webapi-project/ 我们最近发布 ...
- ASP.NET MVC & WebApi 中实现Cors来让Ajax可以跨域访问 (转载)
什么是Cors? CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing).它允许浏览器向跨源服务器,发出XMLHttpReq ...
- asp.net mvc webapi 实用的接口加密方法
在很多项目中,因为webapi是对外开放的,这个时候,我们就要得考虑接口交换数据的安全性. 安全机制也比较多,如andriod与webapi 交换数据的时候,可以走双向证书方法,但是开发成本比较大, ...
- Asp.Net MVC WebAPI的创建与前台Jquery ajax后台HttpClient调用详解
1.什么是WebApi,它有什么用途? Web API是一个比较宽泛的概念.这里我们提到Web API特指ASP.NET MVC Web API.在新出的MVC中,增加了WebAPI,用于提供REST ...
- ASP.NET MVC WebAPI 资源整理
注:这是收集给公司同事学习的资料,入门级别的. 使用ASP.Net WebAPI构建REST服务(一)——简单的示例 http://blog.csdn.net/mengzhengjie/article ...
- asp.net mvc webapi 实用的接口加密方法(转载)
在很多项目中,因为webapi是对外开放的,这个时候,我们就要得考虑接口交换数据的安全性. 安全机制也比较多,如andriod与webapi 交换数据的时候,可以走双向证书方法,但是开发成本比较大, ...
随机推荐
- 【windows】常用快捷键
浏览器 ctrl shift del 清除网页缓存 文件系统 win+e 打开文件窗口 win+r 运行命令窗口 win+l 锁定桌面 win+m 最小化窗口 ctrl+shift +n 创建文件夹
- 怎么检测JDK环境变量是否配置正确
怎么检测JDK环境变量是否配置正确.. 点击开始--运行--输入cmd,点击确定. 在命令行窗口输入java 然后Enter.没有出现java既不是内部命令也不是外部命令.说明配置是正确的. 在命令 ...
- php Yii2使用registerJs或registerCss报错syntax error, unexpected end of file
解决方法: 注册时$js=<<<JS .....JS;//结尾处JS;应单独成行并且没有空格 JS;//这样就会报错,多了空格JS;//这样就不会
- 大咖云集!IMWebConf 2017 前端大会即将在深圳盛大开幕
2017年火热的夏季,一年一度的IMWebConf强势来袭!9月16日,深圳科兴国际会议中心,我们将全心全意打造一场前端盛宴,恭候各位的光临. 作为一名前端老鸟,笔者有幸也参与了本次的大会的主题分享& ...
- opencv VideoCapture使用示例
在centos7下验证VideoCapture功能. 1 opencv处理视频时要使用ffmpeg,这里使用添加源的方式安装,分为3步 1.1 先安装EPEL Release,使用其他的repo源,所 ...
- django框架(View)
-------------------URLconf-------------------1.设置 1.在settings.py文件中通过ROOT_URLCONF指定根级url的配置 2.urlpat ...
- Thread类源码剖析
目录 1.引子 2.JVM线程状态 3.Thread常用方法 4.拓展点 一.引子 说来也有些汗颜,搞了几年java,忽然发现竟然没拜读过java.lang.Thread类源码,这次特地拿出来晒一晒. ...
- java的jar包加密
由于项目要求(虽然我觉得代码没什么机密可言...),写好的jar包需要做一定加密处理 这里提供两种办法,一种奇葩,一种通用 1. 直接修改jar文件: 具体步骤: 在代码中插入一段不会运行的到的代码 ...
- 代码精简之Lombok
JavaWeb项目开发中,JavaBean总是不可避免的出现,随之而来的就是大量的getter.setter方法,虽然大部分的开发工具(比如Eclipse等)都支持自动生成这些东西,但是一旦Bean里 ...
- ORACLE SEQUENCE的简单介绍
先假设有这么一个表: create table S_Depart ( DepartId INT not null, DepartName NVARCHAR2() not null, DepartOrd ...