• 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

  • 相关阅读:
    【转】C#中的虚方法
    【转】ASP.NET 2.0中Page事件的执行顺序
    OWC ChartSpace控件的使用
    Ext对基本类型的扩展
    OWC PivotTable的使用方法
    .net中线程同步的典型场景和问题(1)
    python中使用汉字
    如何取消后台线程的执行
    yaffs2根文件系统的构建过程
    Fuck self.delegate = self
  • 原文地址:https://www.cnblogs.com/lightluomeng/p/4743575.html
Copyright © 2020-2023  润新知