.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 交换数据的时候,可以走双向证书方法,但是开发成本比较大, ...
随机推荐
- C3P0数据库连接池使用中的问题
java.io.FileNotFoundException: D:\javaStudy\javaee\.metadata\.plugins\org.eclipse.wst.server.core\tm ...
- Ext.NET webform
Ext.NET是基于跨浏览器的ExtJS库和.NET Framework的一套支持ASP.NET AJAX的非开源Web控件,包含有丰富的Ajax运用,其前身是Coolite[1] .
- 对vue生命周期/钩子函数的理解
对于实现页面逻辑交互等效果,我们必须知晓vue的生命周期,才能愉快的玩耍,知道我们写的东西应该挂载到哪里,vue官方给出的api讲解的那叫一个简单啊,如下: 所有的生命周期钩子自动绑定this上下文到 ...
- 【MySQL】 清除等待连接
由于MySQL突然新增了很多连接,超出了my.cnf所设置的最大连接数,MySQL服务无法访问,这里通过Shell脚本来删掉Sleep连接 方式1 清除连接进程 #!/bin/bash #------ ...
- 【可视化】div背景半透明
css实现元素半透明使用 opacity:0.x 实现背景色半透明:rgba(a,b,c,x); x为透明度0,到1
- 基于.NET CORE微服务框架 -谈谈surging API网关
1.前言 对于最近surging更新的API 网关大家也有所关注,也收到了不少反馈提出是否能介绍下Api网关,那么我们将在此篇文章中剥析下surging的Api 网关 开源地址:https://git ...
- 用DOS命令来运行Java代码
用DOS命令来运行Java代码.. ----------------- Demo.java public class Demo { public static void main(String[] a ...
- [Caffe]使用经验积累
Caffe使用经验积累 本贴记录Caffe编译好了,使用过程的常用命令与常见错误解决方式.如果对编译过程还存在问题,请参考史上最全的caffe安装过程配置Caffe环境. 1 使用方法 训练网络 xx ...
- log4j日志框架学习
初识Log4j: log4j有三个部分: 1.loggers 负责捕获日志信息. 2.appenders 负责输出信息到不同的目的地 ...
- appium 判断app是否安装
#coding:utf-8 #Import the common package import os import unittest from appium import webdriver from ...