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

原文地址:https://www.cnblogs.com/lightluomeng/p/4743575.html