.NET异步性能测试(ASP.NET MVC WebAPI异步方法)

Posted DotNet

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了.NET异步性能测试(ASP.NET MVC WebAPI异步方法)相关的知识,希望对你有一定的参考价值。


来源:深蓝医生

cnblogs.com/bluedoctor/p/7562705.html


今年做的产品公司这两天刚刚开了发布会,稍微清闲下来,想想我们做的产品还有没有性能优化空间,于是想到了.Net的异步可以优化性能,但到底能够提升多大的比例呢?


恰好有一个朋友正在做各种语言的异步性能测试(有关异步和同步的问题,请参考客《AIO与BIO接口性能对比》(http://blog.csdn.net/scythe666/article/details/51984111)),于是我今天写了一个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 = 100;

        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 = 50;

        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 = 0; 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 = 0; 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 = 100;

        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 = 50;

        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 = 0; 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 = 0; 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 = 0; i < TaskNumber; i++)

{

    Task task = Task.Run<string>(()=> ExecuteBIO(SleepTime));

    taskArr[i] = task;


}

Task.WaitAll(taskArr);


异步调用:


for (int i = 0; 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程序,使用异步多线程,可以极大的提高系统的吞吐量。


看完本文有收获?请转发分享给更多人

关注「DotNet」,提升.Net技能 

以上是关于.NET异步性能测试(ASP.NET MVC WebAPI异步方法)的主要内容,如果未能解决你的问题,请参考以下文章

ASP.NET mvc4 Controllder 同步还是异步

ASP.NET MVC 4 异步加载控制器

ASP.NET MVC中使用异步控制器

ASP.NET MVC 4 异步子动作

具有 Task.Run 性能的 ASP.NET Web API 2 异步操作方法

使用 jQuery 和 ASP.NET MVC 异步上传文件,无需表单提交和异步