首页 > 娱乐前沿 > 热点
对ASP.NET异步编程的一点理解
佚名 2015-10-31 11:25:09

本来这篇博文想探讨下异步中的异常操作,但自己在做异步测试的时候,又对 ASP.NET 异步有了新的认识,可以说自己之前对异步的理解还是有些问题,先列一下这篇博文的三个解惑点:

之前测试过异步中的同步(很多种情况),这次我们把测试代码写更复杂些(异步中再进行异步),代码如下:

[Route("")]
[HttpGet]
public async Task<string> Index()
{
    System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId1:" + Thread.CurrentThread.ManagedThreadId);
    var result = await Test();
    System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId6:" + Thread.CurrentThread.ManagedThreadId);
    return result;
}
public static async Task<string> Test()
{
    System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId2:" + Thread.CurrentThread.ManagedThreadId);
    using (var client = new HttpClient())
    {
        var response = await client.GetAsync("http://stackoverflow.com/questions/14996529/why-is-my-async-asp-net-web-api-controller-blocking-the-main-thread");
        await Test2();
        System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId5:" + Thread.CurrentThread.ManagedThreadId);
        return await response.Content.ReadAsStringAsync();
    }
}
public static async Task<string> Test2()
{
    System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId3:" + Thread.CurrentThread.ManagedThreadId);
    using (var client = new HttpClient())
    {
        var response = await client.GetAsync("http://stackoverflow.com/questions/33408905/pgadminiii-bug-on-query-tool");
        System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId4:" + Thread.CurrentThread.ManagedThreadId);
        return await response.Content.ReadAsStringAsync();
    }
}

输出结果(执行四次):

Thread.CurrentThread.ManagedThreadId1:8
Thread.CurrentThread.ManagedThreadId2:8
Thread.CurrentThread.ManagedThreadId3:6
Thread.CurrentThread.ManagedThreadId4:6
Thread.CurrentThread.ManagedThreadId5:6
Thread.CurrentThread.ManagedThreadId6:6
Thread.CurrentThread.ManagedThreadId1:7
Thread.CurrentThread.ManagedThreadId2:7
Thread.CurrentThread.ManagedThreadId3:8
Thread.CurrentThread.ManagedThreadId4:7
Thread.CurrentThread.ManagedThreadId5:7
Thread.CurrentThread.ManagedThreadId6:7
Thread.CurrentThread.ManagedThreadId1:5
Thread.CurrentThread.ManagedThreadId2:5
Thread.CurrentThread.ManagedThreadId3:5
Thread.CurrentThread.ManagedThreadId4:6
Thread.CurrentThread.ManagedThreadId5:6
Thread.CurrentThread.ManagedThreadId6:6
Thread.CurrentThread.ManagedThreadId1:8
Thread.CurrentThread.ManagedThreadId2:8
Thread.CurrentThread.ManagedThreadId3:8
Thread.CurrentThread.ManagedThreadId4:8
Thread.CurrentThread.ManagedThreadId5:8
Thread.CurrentThread.ManagedThreadId6:8

这个测试方法,我执行了无数次,大致就是上面的四种情况,我当时看到输出结果,其实是很凌乱的,我也大家也一样,并心里有一些疑问:你这真是异步编程吗?为啥线程千奇百怪?并且最后那个还只有一个线程,这和同步有啥区别???

针对上面这个疑问,我想了很久,并对自己产生了一些质疑的声音:你每天都在写 async await 代码,你真的了解它吗???然后我又重新找到上面那篇 jesse liu 的博文,反复读了很多篇,最后终于有了一些“顿悟”,结合上面的测试代码,我大致画了一张示意图:

对 ASP.NET 异步编程的一点理解 - 技术文摘 | 玩赚乐 1

结合上面的图,我说一下自己的理解,在做测试的时候,HttpClient.GetAsync 尽量让它执行时间长些,比如请求的 URL 可以是 stackoverflow 或 github(原因你懂得!),因为有个时间差,这样我们可以更好的了解线程的执行情况,上面图中“线程1、线程1x、线程3x、线程4x”等等,这些并不是不同线程,也就是说线程1有可能等于线程1x或线程3x。。。从上面的输出结果就可以看出,用线程x来表示两个输出之间所经历的 await 次数,这就证明了一个疑惑:await 并不一定会创建和之前不一样的线程。

到底什么是异步???我个人觉得,async 异步是一个伪概念,await 等待才是精髓,一个线程可以响应多个请求,如果是同步编程,一个线程在处理某一个请求的时候阻塞了(比如上面测试代码中的 HttpClient.GetAsync 网络操作),那么这个线程就会一直等待它处理,在这个等待的过程中,那么其他请求就不能再使用这个线程,又因为 IIS 线程池中的线程数量有限,那么同步编程下,高并发将是一个头疼的问题,试想一下,如果线程池中的线程数量为 100 个,这 100 个线程在同时处理 100 个请求的时候,都悲催的阻塞掉了,这时候第 101 个请求将无法执行,那么并发量就是 100。

接上面,同样的处理过程,如果是异步编程,那将是什么情况呢?比如一个线程在处理某一个请求的时候,执行到 await 操作,那么这个线程将会释放回到线程池,然后进行等待,等待的过程中,原来的那个线程就可以处理其他请求或者这个请求的其他操作,注意等待并不是线程等待,而是操作等待,我原来就很不理解这个地方,如果是线程等待,就表示这个线程会一直等待它完成,那和同步编程就是一样的了,所以这种理解是错误的,你可以这样理解:await 等待的过程中,没有线程!!!

再接上面,等待操作完成之后,这时候就会从线程池中随机拿一个线程继续执行,拿到的这个线程有可能是 await 操作刚刚释放掉的,但也有可能是其他线程,上图中的 2-6 操作就是这样,一图胜千言:

对 ASP.NET 异步编程的一点理解 - 技术文摘 | 玩赚乐 2

了解了整个过程之后,你才会明白 async await 到底是什么鬼?以及它真正的用武之地是什么?简单总结几点内容:

另外,在 ASP.NET 应用程序中,我们可以使用Thread.CurrentThread来访问当前的执行线程,我之前想做这样一个测试,让当前执行线程 Sleep 一段时间,看看其他线程会不会执行,但Thread.CurrentThread并没有 Sleep 方法,而必须这样访问Thread.Sleep(int millisecondsTimeout),如果这样执行这段代码,那么当前线程将会 Sleep,但其他线程并不会在它 Sleep 的时候,而继续执行,为什么?因为CPU 在同一时间段内只能执行一个线程。

了解了 async await 到底是什么鬼后,博文一开始剩下的两个有关异步操作中的异常问题,现在理解起来就非常容易了:

如果我们想让某一个异步方法,在执行抛出异常的时候,而不影响其他异步方法,那我们就 catch 而不 throw,比如我们的测试代码:

[Route("")]
[HttpGet]
public async Task<string> Index()
{
    System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId1:" + Thread.CurrentThread.ManagedThreadId);
    var result = await Test();
    System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId6:" + Thread.CurrentThread.ManagedThreadId);
    return result;
}
public static async Task<string> Test()
{
    try
    {
        System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId2:" + Thread.CurrentThread.ManagedThreadId);
        using (var client = new HttpClient())
        {
            var response = await client.GetAsync("http://stackoverflow.com/questions/33408905/pgadminiii-bug-on-query-tool");
            System.Diagnostics.Debug.WriteLine("Thread.CurrentThread.ManagedThreadId3:" + Thread.CurrentThread.ManagedThreadId);
            throw new Exception("test exception");//这里出现了异常
            return await response.Content.ReadAsStringAsync();
        }
    }
    catch (Exception ex)
    {
        System.Diagnostics.Debug.WriteLine("异常信息:" + ex.Message);
        return "";
        //throw ex;
    }
}

这样的效果就是 Index 页面不会报错,并且也不会影响其他方法执行,现在发现当时疑惑这个问题的时候,还蛮白痴的,还是那句话,异常和异步没半毛钱关系,相同的问题,同步也是这样进行处理的。

博文内容有点多,如果你不愿花时间看,可以直接记住这段话:如果你的应用程序请求访问很少(并发很小),异步和同步将是一样的效果,异步化改造是毫无意义的,而如果你的应用程序请求访问很多(并发很大),那么效果显而易见,如果使用异步将会为你省掉几台服务器的钱,但代码异步化并不能使你的应用程序执行速度加快(指的是代码执行速度),垃圾代码还是垃圾代码,并不会有任何的改善,所以,写好“好的代码”很重要!!!

上一篇  下一篇

I 相关 / Other

看互联网大佬合并潮?不如学大佬巧妙推广App

导语: 跟以往冤家抱团的反转例子相比,团队撕得一地鸡毛,资本该合照样合的美团跟大众点评,才是真正的理性

有一个肾就够却长了两个,主要是为了对称

长文预警!多图预警!这个问题背后有着非常重大的意义!就像 @大木博士所说的那样,肾脏是从中胚层分化来的

CarPlay进中国,你终于有机会摆脱难看的车载系统界面了

最近,2016 款凯迪拉克 ATS-L 在中国发售了。和去年旧款相比,新车并没有涨价,全系 28.88 万到 42.88 万

胡子节来了,嘉士伯做了款啤酒剃须膏

全球第四大啤酒生产商嘉士伯在今年六月推出了男士美容产品,用大麦、酵母、啤酒花和啤酒为基底,做了一套洗

这6款关于植物的设计或许能为你的居室加分|这个设计了不起

家居装修中,绿色植物的点缀是必不可少的,很多环境中因为有植物的装饰而变得生机勃勃。本周末的特辑我们聚

I 热点 / Hot