.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 交换数据的时候,可以走双向证书方法,但是开发成本比较大, ...
随机推荐
- 理解JavaScript的立即调用函数表达式(IIFE)
首先这是js的一种函数调用写法,叫立即执行函数表达式(IIFE,即immediately-invoked function expression).顾名思义IIFE可以让你的函数立即得到执行(废话). ...
- [js高手之路]构造函数的基本特性与优缺点
上文,通过基本的对象创建问题了解了构造函数,本文,我们接着上文继续了解构造函数的基本特性,以及优缺点. 每个对象上面都有一个constructor属性( 严格意义上来说,是原型上的,对象是通过查找到原 ...
- TechSmith Camtasia Studio屏幕录像编辑工具
TechSmith Camtasia Studio屏幕录像编辑工具... --------------------------------------------------------- ----- ...
- 如何理解 Python 的赋值逻辑
摘要: 如果你学过 C 语言,那么当你初见 Python 时可能会觉得 Python 的赋值方式略有诡异:好像差不多,但又好像哪里有点不太对劲. 本文比较并解释了这种赋值逻辑上的差异.回答了为什么需要 ...
- 同形异义字:看我如何拿到TaoBao.com的解析权
*本文原创作者:expsky@MS509Team,本文属FreeBuf原创奖励计划 同形异义字钓鱼攻击号称"几乎无法检测",是最狡猾的钓鱼攻击!这种攻击产生的原因是国际化域名IDN ...
- 【渗透课程】第五篇-SQL注入的原理
哈哈哈,讲到注入了.我想给大家讲注入的原理.这个我们前面的前言篇就说过,所谓的SQL注入就是,绕过数据库验证机制直接执行SQL语句.怎么说呢,我们先讲一个概念吧! 网站和网页的区别 单纯的网页是静态的 ...
- StringBuffer .insert方法输出电话号码格式
package ch11; import java.util.Scanner; /** * Created by liwenj on 2017/7/21. */public class T7 { pu ...
- cocos quick lua 输入框点击穿透的问题处理方案。
条件:当前版本quick-3.3 -lua,系统 win7. 问题:在输入框(textField或者editbox,下文"输入框"就代表这两种)打开的情况下弹出其他界面盖住输入框, ...
- [js高手之路]深入浅出webpack系列1-安装与基本打包用法和命令参数
webpack,我想大家应该都知道或者听过,Webpack是前端一个工具,可以让各个模块进行加载,预处理,再进行打包.现代的前端开发很多环境都依赖webpack构建,比如vue官方就推荐使用webpa ...
- Spring @ResponseBody 返回中文乱码问题
详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt102 今天在使用spring 的时候,发现中文返回的是乱码. 经过研究发现, ...