首页 新闻 赞助 找找看

AsyncController到底有何作用?如何实现?

1
悬赏园豆:100 [已解决问题] 解决于 2017-06-28 18:22

先引用一段Rick Anderson的话来说我们心目中的asynccontroller的好处:

 

In web applications that sees a large number of concurrent requests at start-up or has a bursty load (where concurrency increases suddenly), making these web service calls asynchronous will increase the responsiveness of your application. An asynchronous request takes the same amount of time to process as a synchronous request. For example, if a request makes a web service call that requires two seconds to complete, the request takes two seconds whether it is performed synchronously or asynchronously. However, during an asynchronous call, a thread is not blocked  from responding to other requests while it waits for the first request to complete. Therefore, asynchronous requests prevent request queuing and thread pool growth when there are many concurrent requests that invoke long-running operations.

 

基于上面这段话中下划线的部分,我写了一个简单的测试,一个同步方法处理,一个异步方法处理。代码如下:

 1 using System;
 2 using System.Threading;
 3 using System.Threading.Tasks;
 4 using System.Web.Mvc;
 5 
 6 namespace LoadTest.Web.Controllers
 7 {
 8     public class TestController : AsyncController
 9     {
10         public string T1()
11         {
12             Thread.Sleep(100);
13             return "done-" + DateTime.Now;
14         }
15 
16         public async Task<string> T2()
17         {
18             return await Task.Run(() =>
19             {
20                 Thread.Sleep(100);
21                 return "done-" + DateTime.Now;
22             });
23         }
24     }
25 }

【测试环境及参数】:

win7 IIS7.5(最大并发工作线程数=10),最大并发连接数默认设置为42亿,应用程序池队列长度默认设置为1000.

 

在我的测试中,模拟100个并发用户请求,共发起1000个请求,看这2个方法对于IIS的吞吐率(每秒处理请求数)是多大。

 

【预期结果】:

按照AsyncController的功能预期,异步方法的吞吐率应该是同步方法的很多倍,并且异步方法的CPU利用率要高于同步方法,因为IIS每秒处理请求数预计会提高。

 

【实际结果】:

实际结果却是两个方法差不多,CPU利用率也没有变,甚至同步方法的吞吐率还稍微高于异步。我执行了很多次测试都是这个结果。

 

问:

asynccontroller是这样使用的吗?如果是,那为什么并不能提高IIS每秒处理请求数呢?

 

求大神指点啊。

问题补充:

不说Thread.Sleep(100),就算是换做下面的2个测试方法,结果仍然是一样的,同步的吞吐率稍微高于异步的:

 1 public string T1()
 2 {
 3     using (var webClient = new WebClient())
 4     {
 5         return webClient.DownloadString("http://zzk.cnblogs.com/b");
 6     }
 7 }
 8 
 9 public async Task<string> T2()
10 {
11     using (var httpClient = new HttpClient())
12     {
13         var response = await httpClient.GetAsync(new Uri("http://zzk.cnblogs.com/b"));
14         return (await response.Content.ReadAsAsync<string>());
15     }
16 }
Leo C.W的主页 Leo C.W | 初学一级 | 园豆:155
提问于:2014-07-17 10:01
< >
分享
最佳答案
1

我基本上可以肯定是你测试并发的程序有问题,你可以贴出来看一下,我这边自己写了个小程序,经过测试,异步action的并发率要高的多。

首先是mvc里准备两个action,一个同步一个异步:

        public ActionResult Index1()
        {
            Thread.Sleep(2000);

            return Content("synchronized");
        }

        public async Task<ActionResult> Index2()
        {
            await Task.Delay(2000);

            return Content("asynchronized");
        }

同样的都是耗时2000ms的操作,然后先用浏览器分别访问一下/home/index1和/home/index2,这样是为了让web app运行起来,以免等我们测试并发的时候才app start,导致影响测试结果。

然后是一个console小程序:

        static void Main(string[] args)
        {
            var syncUrl = "http://localhost:63199/Home/Index1";
            var asyncUrl = "http://localhost:63199/Home/Index2";
            var count = 200;

            Benchmark(asyncUrl, count);
            Benchmark(syncUrl, count);

            Console.Read();            
        }

        static void Benchmark(string url, int count)
        {
            var stopwatch = new Stopwatch();
            stopwatch.Start();

            var threads = new List<Thread>();
            var countdown = new CountdownEvent(count);
            for (int i = 0; i < count; i++)
            {
                threads.Add(new Thread(() =>
                {
                    using (var client = new WebClient())
                    {
                        client.DownloadData(url);
                        countdown.Signal();
                    }
                }));
            }

            for (int i = 0; i < count; i++)
            {
                threads[i].Start();
            }

            while (!countdown.IsSet) ;

            stopwatch.Stop();

            Console.WriteLine(string.Format("{0} costs {1} ms", url, stopwatch.ElapsedMilliseconds.ToString()));
        }

测试结果

http://localhost:63199/Home/Index2 costs 2255 ms
http://localhost:63199/Home/Index1 costs 36152 ms

可以看到异步action总耗时只有同步操作的1/18,可见并发率高了非常多。

收获园豆:100
水牛刀刀 | 大侠五级 |园豆:6350 | 2014-07-22 10:52

我用你的代码测试,结果跟你的不一样啊。可以发完整的代码给我吗?2838零967零 at qqdotcom。谢谢!

Leo C.W | 园豆:155 (初学一级) | 2014-07-22 11:59

同意,关键就是请求的时候也要多线程才是并发请求。不然时间都浪费在请求上了。

咸鱼.net | 园豆:216 (菜鸟二级) | 2016-07-28 00:07
其他回答(5)
0

不管是不是异步,单看这一条语句的作用:Thread.Sleep(100),在 100 毫秒内你发起的请求越多,被挂起的线程就越多。

Launcher | 园豆:45045 (高人七级) | 2014-07-17 10:05

谢谢回复。Thread.Sleep如果是在异步的线程里面执行的,应该不是处理当前请求的线程吧。另外,请看问题补充。

支持(0) 反对(0) Leo C.W | 园豆:155 (初学一级) | 2014-07-17 10:13

@Leo C.W: “异步线程”也是线程,也是线程池中的线程,线程只要被分配后就会占用系统资源。

客户端测试的时候需要使用 httpClient.GetAsync 和 response.Content.ReadAsAsync.

支持(0) 反对(0) Launcher | 园豆:45045 (高人七级) | 2014-07-17 10:25

@Launcher: 补充问题就是使用的这种方式,效果一样的啊。

支持(0) 反对(0) Leo C.W | 园豆:155 (初学一级) | 2014-07-17 12:54

@Leo C.W: 是吗?把你的测试代码贴出来看看。

支持(0) 反对(0) Launcher | 园豆:45045 (高人七级) | 2014-07-17 13:00

@Launcher: 补充问题里面的代码就是测试代码啊。我用的AB进行的压力测试。

支持(0) 反对(0) Leo C.W | 园豆:155 (初学一级) | 2014-07-17 14:59

@Leo C.W: 你的客户端代码应该这样写:

 httpClient.GetAsync().ContinueWith(response.Content.ReadAsAsync<string>())

支持(0) 反对(0) Launcher | 园豆:45045 (高人七级) | 2014-07-17 15:03

@Launcher: 这个代码不得行啊,都不能编译的。

支持(0) 反对(0) Leo C.W | 园豆:155 (初学一级) | 2014-07-17 15:21

@Leo C.W: 我总以为你掌握了 TPL 编程。

httpClient.GetAsync(new Uri("http://zzk.cnblogs.com/b")).ContinueWith(t => t.Result.Content.ReadAsStringAsync());

支持(0) 反对(0) Launcher | 园豆:45045 (高人七级) | 2014-07-17 15:49

@Launcher: 你把整个action的完整的代码贴出来吧。你发的2份代码不能编译。

支持(0) 反对(0) Leo C.W | 园豆:155 (初学一级) | 2014-07-17 16:35

@Leo C.W: 

using (var httpClient = new HttpClient())

{

         httpClient.GetAsync(new Uri("http://zzk.cnblogs.com/b")).ContinueWith(t => t.Result.Content.ReadAsStringAsync());
}

支持(0) 反对(0) Launcher | 园豆:45045 (高人七级) | 2014-07-17 16:49

@Launcher: 我用下面这个方法测试,结果一摸一样,同步方法的吞吐率略高于异步。

1 public async Task<Task<string>> Tt3()
2 {
3     using (var httpClient = new HttpClient())
4     {
5         return await httpClient.GetAsync(new Uri(Url))
6             .ContinueWith(t => t.Result.Content.ReadAsStringAsync());
7     }
8 }
支持(0) 反对(0) Leo C.W | 园豆:155 (初学一级) | 2014-07-19 09:05

@Leo C.W: 已知测试服务器 Server 和测试客户端 Client:

1、Server 端的被测试方法为:

public async Task<byte[]> T2()
{
  using (var httpClient = new HttpClient())
  {
    var response = await httpClient.GetAsync(new Uri("http://zzk.cnblogs.com/b"));
    return (await response.Content.ReadAsAsync<string>());
  }
}

2、Client 端用于发起调用 Server 端被测试方法的代码为:

public void TestMethod()
{
  using (var httpClient = new HttpClient())
  {
    httpClient.GetAsync(new Uri("http://zzk.cnblogs.com/b")).ContinueWith(t => t.Result.Content.ReadAsStringAsync());
  }
}

3、将 Client 端部署到 2 台以上的客户机上,注意观察 Server 端机器的 CPU 和网络使用率,如果不足,则增加 Client 部署的机器数目。

 

PS: ReadAsAsync<string>() 返回的字符串不宜过大,控制在 4096 以内比较合适。

 

支持(0) 反对(0) Launcher | 园豆:45045 (高人七级) | 2014-07-21 09:03
0

异步任务和异步action是两回事吧。。。

我的理解:异步action应该是释放当前处理http请求的线程,让它可以继续处理其他的http请求。等这个异步action执行完毕,再返回到客户端。异步action不适合高cpu的操作,适合磁盘IO等不依赖于cpu的操作

ExploreForward | 园豆:18 (初学一级) | 2014-07-17 16:38
0

100并发很低的说。。。IIS玩的是IOCP+线程池的模式,即使应用是同步的,也足以应付区区100并发。

非阻塞IO关注的是scalable,而不是缩减单个请求的时间。事实上因为多了IO事件相关处理,单个请求反而稍微慢了。如果应用需要多次与后端通信,或者后端较慢,那么高并发下异步模式的优势就出来了,至少理论上。。。

另外sleep这种玩法是错的,不但需要完成与同步模式一样的操作,还多了些无意义的运算

FlyingCat | 园豆:202 (菜鸟二级) | 2014-07-18 11:26

谢谢回复。你先参考下这篇文章的结果吧:http://www.cnblogs.com/leotsai/p/understanding-iis-multithreading-system.html

支持(0) 反对(0) Leo C.W | 园豆:155 (初学一级) | 2014-07-18 17:37

@Leo C.W: 牛头不搭马嘴。线程池是ASP.NET自己维护的,这个跟IIS的设置有条毛关系。而且我都不好意思说出你的那篇文章既不“深入”也无意义。无论那些设置具体含义是什么,都不过是为了保护系统资源而加上的人为瓶颈,只要你喜欢,你调多大都可以,顶多叫理论最大并发数,叫做“真正的并发处理能力”就略为搞笑了,hold住一个请求是要花资源的,即使win很牛叉,机器硬件很牛逼,能同时吞掉100K个http请求,但是应用一秒只能处理1K个请求,下一秒又来了100K个请求,那咋办。再说我只知道ASP.NET有最大线程数。。。

支持(0) 反对(0) FlyingCat | 园豆:202 (菜鸟二级) | 2014-07-18 21:41

@FlyingCat: 就问你2个简单问题,如果你答对了,那你就是真牛逼。

假设:有一个action,有一个计数器,进入action时计数器+1,action执行完计数器-1,该方法执行时间为50毫秒。在压力测试中,模拟2000用户并发,共1万个请求。IIS设置最大并发连接数为1000,队列长度为1000。测试环境为win7的IIS 7.5。

问:

1. 测试运行完成后,计数器最大值为多少?

2. 这1万个请求执行完大概要多久?

支持(0) 反对(0) Leo C.W | 园豆:155 (初学一级) | 2014-07-19 09:19

@Leo C.W: 呵呵,我何时说过我牛逼了,我还真不会答,答了也没意义,反正对你来说,线程堵塞没关系,代码慢也没关系,调个IIS设置就行了。还异步个什么呀。

支持(0) 反对(0) FlyingCat | 园豆:202 (菜鸟二级) | 2014-07-19 09:54

@Leo C.W: 

看到这个有感而发,楼主可以参考 https://docs.microsoft.com/en-us/aspnet/mvc/overview/performance/using-asynchronous-methods-in-aspnet-mvc-4 这个,这里面说的很清楚,

Windows 7, Windows Vista and all Windows client operating systems have a maximum of 10 concurrent requests. You'll need a Windows Server operating system to see the benefits of asynchronous methods under high load.
必须用winserver的测试才能体现async的优势,不知道楼主最后是如何试验的,看@FlyingCat 这个逼的回答,完全是来捣乱的

支持(0) 反对(0) 泡椒泡着吃 | 园豆:200 (初学一级) | 2018-02-08 13:48
0

有一个问题,Thread.Sleep会阻塞UI进程,但是Task.Delay不会,是异步延迟。所以你本身的方法写的有问题啊!

不应该是:

Thread.Sleep(100);

return "done-" + DateTime.Now;

这个才是问题的关键所在吧!

tianya22110 | 园豆:228 (菜鸟二级) | 2014-12-19 14:16

Thread.Sleep阻塞的是UI进程还是当前线程,这点你确定?

支持(0) 反对(0) Leo C.W | 园豆:155 (初学一级) | 2014-12-19 14:17

@Leo C.W: 当然是UI进程啊!

支持(0) 反对(0) tianya22110 | 园豆:228 (菜鸟二级) | 2014-12-19 14:27

@Leo C.W: 
MSDN上的解释是:

// The following method runs synchronously, despite the use of async.

// You cannot move or resize the Form1 window while Thread.Sleep

// is running because the UI thread is blocked.

public async Task<string> WaitSynchronously()

{

// Add a using directive for System.Threading.

Thread.Sleep(10000);

return "Finished";

}

支持(0) 反对(0) tianya22110 | 园豆:228 (菜鸟二级) | 2014-12-19 14:33

@Leo C.W: 请问,我是回答错了么,大神?

支持(0) 反对(0) tianya22110 | 园豆:228 (菜鸟二级) | 2015-01-13 09:00
0

线程开多了,性能就降低了。所以异步的话,不用持续的开新线程而复用等待状态的线程,自然就性能更高了

孤城唯一客 | 园豆:204 (菜鸟二级) | 2019-07-08 10:31
清除回答草稿
   您需要登录以后才能回答,未注册用户请先注册