.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 交换数据的时候,可以走双向证书方法,但是开发成本比较大, ...
随机推荐
- 陈年佳酿之 - Winform ListView 控件 double click 事件中获取选中的row与column
背景 最近收到了一个关于以前项目的维护请求,那时的楼主还是刚刚工作的小青年~~~ 项目之前使用的是.net/winform.今天重新打开代码,看着之前在FrameWork2.0下面的代码, 满满的回忆 ...
- [Python] python3 文件操作:从键盘输入、打开关闭文件、读取写入文件、重命名与删除文件等
1.从键盘输入 Python 2有两个内置的函数用于从标准输入读取数据,默认情况下来自键盘.这两个函数分别是:input()和raw_input(). Python 3中,不建议使用raw_input ...
- 关于Java String 类型转换时null的问题
开发中经常遇到从集合类List.Map中取出数据转换为String的问题,这里如果处理不好,经常会遇到空指针异常java.lang.NullPointerException,在此总结一下常用转换为St ...
- BOOT BIOS UEFI
1. 什么是BOOT? BOOT其实是你按开机键之后,在电脑微软操作系统运行之前就自动运行的一段小程序.表象上就是windows引导画面之前那些黑屏蓝屏的英文那个阶段就是BOOT了.通过这段小程序,它 ...
- TP-Link路由器的设置
TP-Link路由器的设置.. 路由器中TP-Link是公认比较好用的品牌. 将TP-Link路由器的Lan与电脑连接.拔掉Wan口上的线. 注:tplink的路由器默认IP基本都是192.168.1 ...
- C#多线程的用法7-线程间的协作ManualResetEvent
ManualResetEvent:手动重置事件,它用于线程间同步时用法非常简单也易于理解. private static void MultiThreadSynergicWithManualReset ...
- 递归的二叉查找树Java实现
package practice; public class TestMain { public static void main(String[] args) { int[] ao = {50,18 ...
- python中字符串中一些函数的用法
1..capitalize():字符串的首字母大写: 2..count():字符串中的某个字母的个数: 3..center(50,'-'):对象居中,且左右用'-'补齐: 4..encode():吧字 ...
- copy和mutableCopy都是浅拷贝!!!------你被骗了很多年
所有系统容器类的copy或mutableCopy方法,都是浅拷贝!!! (ps:什么是容器?比如NSArray,NSMutableArray,NSDictionary,NSMutableDiction ...
- (一)Builder(建造者)模式
我们一般在构建javabean的对象的时候通常有三种写法: 1.直接通过构造函数传参的方式设置属性,这种方法如果属性过多的话会让构造函数十分臃肿,而且不能灵活的选择只设置某些参数. 2.采用重叠构造区 ...