zoukankan      html  css  js  c++  java
  • CQRS学习——Cqrs补丁,async实验以及实现[其二]

    实验——async什么时候提高吞吐

    async是一个语法糖,用来简化异步编程,主要是让异步编程在书写上接近于同步编程。总的来收,在await的时候,相当于附加上了一个.ContinueWith()。

    至于为什么async能够提高吞吐,是因为通过async方法返回一个Task对象,IIS缩减了工作线程的处理时间长短(切换到了其他线程,且没有阻塞当前线程),从而提高了单位时间的处理量。这里还有其他的一些细节,详情见这篇博文:

    http://www.cnblogs.com/rosanshao/p/3728108.html

    关于async的使用,参考这篇博文:

    http://www.asp.net/mvc/overview/performance/using-asynchronous-methods-in-aspnet-mvc-4

    博主曾经花了一个下午点时间测试性能,就是没有获得期望的结果,用的就是此博文中举出的反例。当时博主心想“既然TPL中的Task+async就能提高性能,那么为什么EF还要特地的提供XXXAsync方法?这不是让别人更加困惑么?”所以博主就打算不用数据库,简单撸一个Task测一测,看看是不是和我想象中的一般逆天。

    根据这篇博客的描述,IIS的线程分为工作线程和IO线程两种,其中工作线程总数被限制在一个阈值,所以减少工作线程的利用效率可以提高吞吐。而在asp.net中,切换线程就分为两种:工作线程->IO线程,工作线程->工作线程(反例)。假定一个工作线程每使用async之前每请求工作1秒,通过切换,IO线程工作的时候,他去处理其他请求,把平均工作时间降为了0.5秒,这样吞吐理想情况下就翻倍了。但是...如果是工作线程->工作线程,虽然对于单个线程而言是减少了,但是其他工作线程又会扔活过来,总体来说没有变化,反而因为交接的问题,性能有所下降...

    先用几个负载测试来支持以上言论

    首先定义一个提供各种操作的辅助类。

    public class BaseFairHelper
        {
            public Task<string> SayHelloTask()
            {
                return Task<string>.Factory.StartNew(() =>
                {
                    Thread.Sleep(1000);
                    return "Hello";
                });
            }
    
            public async Task<string> SayHelloAsync()
            {
                return await Task<string>.Factory.StartNew(() =>
                {
                    Thread.Sleep(1000);
                    return "Hello";
                });
            }
    
            public string SayHello()
            {
                Thread.Sleep(1000);
                return "Hello";
            }
        }
    BaseFairHelper

    1.基础测试

    假定我们任意启动一个Task就可以达到解放IIS工作线程的目的,那么,对于两个Action,一个执行工作量1的同步操作,一个执行工作量1的同步操作外带一个工作量1的一步操作,这两个Action在吞吐以及性能表现上应该相差无几。代码如下:

    /// <summary>
            /// 异步
            /// </summary>
            /// <returns></returns>
            public ActionResult BaseAsync()
            {
                var helper = new BaseFairHelper();
                var task1 = helper.SayHelloTask();
                var str = helper.SayHello();
                task1.Wait();
                return Content(str);
            }
    
            /// <summary>
            /// 对照
            /// </summary>
            /// <returns></returns>
            public ActionResult BaseAsync_()
            {
                var helper = new BaseFairHelper();
                var task1 = helper.SayHelloTask();
                var task2 = helper.SayHelloTask();
                var str = helper.SayHello();
                Task.WaitAll(task1, task2);
                return Content(str);
            }
    
            /// <summary>
            /// 基础对照
            /// </summary>
            /// <returns></returns>
            public ActionResult Base()
            {
                var helper = new BaseFairHelper();
                return Content(helper.SayHello());
            }
    Base Test

    然后使用VS的负载测试,测试模式选为增量,结果如下:

    工作量 情况 吞吐量(min) 吞吐量(max) 时长per请求(max) 时长per请求(min) 吞吐均值 时长均值
    (base)1 同步 8 200 1.02 1.01 145 1.02
    (baseasync)2 同步+异步 8 120 1.76 1.01 98.7 1.53
    (baseasync_)3 同步+异步x2 0 89.4 2.59 1.01 69.5 2.1
                   

    可以发现性能相差明显,但是在低并发情况下,性能表现是我们预期的,高并发的时候,则不然。最大吞吐也不是我们预期的。这点上可以支持“IIS工作线程”有限的观点。

    2.Fair测试

    以上,这是一组对比测试,工作量并不同,现在进行一组工作量相同的测试。其中一个Action执行同步x2的操作,另一个执行同步+异步组合的操作。代码如下:

    public ActionResult FairAsync()
            {
                var helper = new BaseFairHelper();
                var task = helper.SayHelloTask();
                var str = helper.SayHello();
                task.Wait();
                return Content(str);
            }
    
            public ActionResult Fair()
            {
                var helper = new BaseFairHelper();
                var str = helper.SayHello();
                str = helper.SayHello();
                return Content(str);
            }
    Fair Test

    同样适用负载测试,测试模式选为高并发(200用户数):

    工作量 情况 吞吐量(min) 吞吐量(max) 时长per请求(max) 时长per请求(min) 吞吐均值 时长均值
    (fair)2 同步 8 112 2.03 2 85 2.01
    (fairasync)2 同步+异步 20 125 1.85 1 107 1.59

    和低并发(25用户数):

    工作量 情况 吞吐量(min) 吞吐量(max) 时长per请求(max) 时长per请求(min) 吞吐均值 时长均值
    (fair)2 同步 1 12.6 2.03 2 10.7 2.02
    (fairasync)2 同步+异步 2 25 1.02 1 21.3 1.01

    可以看到,由于工作线程争用,导致使用Task的异步方案在高并发的情况下,单个请求的性能有所下降(时长从1->1.85),这也从侧面证明了以上的观点。

    async方法提供吞吐的情况

    这里是我参考的文章:【http://www.dotnetcurry.com/aspnet-mvc/948/webapi-async-performance-aspnet-mvc-application

    以及这篇文章附带的代码:【http://pan.baidu.com/s/1ntxNX4t

    博主针对数据库(EF)的async做了很多次实验,结果发现同步和异步在吞吐以及性能表现上几乎一致(参考文章末尾附件中的测试结果截图)。于是最终返回这篇文章,并针对这篇文章中的代码进行测试,同时结合自己的思考重新编写了测试——结果仍然没有感受到duang一下的特效。所以暂时不纠结了。

     【此处应该有跟进和更新】

    使用async的几个姿势

    对于以下两个异步方法:

    public class AsyncMethods
        {
            public static async Task<string> Async1()
            {
                return await Task<string>.Factory.StartNew((t) =>
                {
                    Task.Delay(1000).Wait();
                    return "hello";
                }, null);
            }
    
            public static async Task<string> Async2()
            {
                return await Task<string>.Factory.StartNew(t =>
                {
                    Thread.Sleep(1000);
                    return "hello";
                }, null);
            }
        }
    async methods

     1.对多个async方法进行同步等待

    [ActionName("IndexAsync2")]
            public async Task<ActionResult> IndexAsync2()
            {
                var task1 = AsyncMethods.Async1();
                var task2 = AsyncMethods.Async2();
                await Task.WhenAll(task1, task2);
                return Content(task2.Result);
            }
    多任务等待

     2.有序执行多个async

    [ActionName("IndexAsync1")]
            public async Task<ActionResult> IndexAsync1()
            {
                string result = await AsyncMethods.Async1();
                result = result + await AsyncMethods.Async2();
                return Content(result);
            }
    有序等待

     3.死锁(反例)

    简单将await方法迁移到同步方法中,都会导致线程死锁(ASP.NET环境下)。由于异步方法执行完成后的操作要求回到调用的上下文(线程),会等待调用上下文。而Wait()方法表示等待异步方法完成。所以你等我我等你,死锁。

    public ActionResult Index1()
            {
                AsyncMethods.Async1().Wait();
                return Content("");
            }
    
            public ActionResult Index2()
            {
                var task1 = AsyncMethods.Async1();
                var task2 = AsyncMethods.Async2();
                Task.WhenAll(task1, task2);
                return Content(task1.Result);
            }
    dead lock

    为何async能够防止ASP.NET工作线程等待

    参考这篇文章:【http://blog.stevensanderson.com/2008/04/05/improve-scalability-in-aspnet-mvc-using-asynchronous-requests/】的图。

    async提高性能的情况

    这是并行编程的情况,总的来说就是充分利用CPU,个人认为这更多的是Task的功劳。async这个关键字更多的像是将一些列的ContinueWith连锁在同一个线程(上下文)之上,防止线程切换。

    在CQRS中实现Command的异步执行

    经过多日的实验和纠结(惭愧),对async的看法有了点转变。async关键字现在给我的感觉,更像是从“骨子里”的异步,因为调用async方法的时候,要求调用方也指明async(或者你可以开一个Task去执行...然而...太蠢)。这感觉是让C#的中的所有方法(指明async)天生就是异步架构的(无端想起了F#)。所以,为Cqrs添加异步功能就分为两块:

    1.为CommandBus添加一个SendAsync的方法

    2.实现一个完全基于异步的Cqrs【想法,想法,只是想法...】【此处应有后续跟进】

    先撸第一个:

     public interface ICommandBus
        {
            void Send<T>(T command) where T : ICommand;
    
            Task SendAsync<T>(T command) where T : ICommand;
        }
    
    void ICommandBus.Send<T>(T command)
            {
                var handler = CommandHandlerSearcher.Find<T>();
    
                #region auditing
    
                var auditInfo = CommandEventAuditInfo.StartNewForCommand<T>(handler.GetType());
                auditInfo.Start();
    
                #endregion
    
                handler.Execute(command);
    
                #region audting
    
                auditInfo.Stop();
    
                #endregion
    
                Test.Configuration.AuditStorage.Save(auditInfo);
            }
    
            public ICommandHandlerSearcher CommandHandlerSearcher { get; set; }
    
            Task ICommandBus.SendAsync<T>(T command)
            {
                ICommandBus bus = this;
                return Task.Factory.StartNew(() => bus.Send(command));
            }
    command bus

    然后是测试结果:

     同时,在修改Auditing支持异步的同时,发现了自己以前实现的Auditing有问题

    至于为什么不考虑实现EventBus支持异步...那是因为,博主当前的工作单元是基于线程的(简单粗暴的将一个Command视为原子操作)。

    与async有关的代码:【http://pan.baidu.com/s/1sjA7gbN

    此篇完成时,所使用的代码:【http://pan.baidu.com/s/1sjsqiZV

  • 相关阅读:
    LeetCode 623. Add One Row to Tree
    LeetCode 894. All Possible Full Binary Trees
    LeetCode 988. Smallest String Starting From Leaf
    LeetCode 979. Distribute Coins in Binary Tree
    LeetCode 814. Binary Tree Pruning
    LeetCode 951. Flip Equivalent Binary Trees
    LeetCode 426. Convert Binary Search Tree to Sorted Doubly Linked List
    LeetCode 889. Construct Binary Tree from Preorder and Postorder Traversal
    LeetCode 687. Longest Univalue Path
    LeetCode 428. Serialize and Deserialize N-ary Tree
  • 原文地址:https://www.cnblogs.com/lightluomeng/p/4743575.html
Copyright © 2011-2022 走看看