CQRS學習——Cqrs補丁,async實驗以及實現[其二]

實驗——async何時提升吞吐

async是一個語法糖,用來簡化異步編程,主要是讓異步編程在書寫上接近於同步編程。總的來收,在await的時候,至關於附加上了一個.ContinueWith()。html

至於爲何async可以提升吞吐,是由於經過async方法返回一個Task對象,IIS縮減了工做線程的處理時間長短(切換到了其餘線程,且沒有阻塞當前線程),從而提升了單位時間的處理量。這裏還有其餘的一些細節,詳情見這篇博文:web

http://www.cnblogs.com/rosanshao/p/3728108.html數據庫

關於async的使用,參考這篇博文:編程

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

博主曾經花了一個下午點時間測試性能,就是沒有得到指望的結果,用的就是此博文中舉出的反例。當時博主心想「既然TPL中的Task+async就能提升性能,那麼爲何EF還要特意的提供XXXAsync方法?這不是讓別人更加困惑麼?」因此博主就打算不用數據庫,簡單擼一個Task測一測,看看是否是和我想象中的通常逆天。架構

根據這篇博客的描述,IIS的線程分爲工做線程和IO線程兩種,其中工做線程總數被限制在一個閾值,因此減小工做線程的利用效率能夠提升吞吐。而在asp.net中,切換線程就分爲兩種:工做線程->IO線程,工做線程->工做線程(反例)。假定一個工做線程每使用async以前每請求工做1秒,經過切換,IO線程工做的時候,他去處理其餘請求,把平均工做時間降爲了0.5秒,這樣吞吐理想狀況下就翻倍了。可是...若是是工做線程->工做線程,雖然對於單個線程而言是減小了,可是其餘工做線程又會扔活過來,整體來講沒有變化,反而由於交接的問題,性能有所降低...併發

先用幾個負載測試來支持以上言論

首先定義一個提供各類操做的輔助類。mvc

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.基礎測試app

假定咱們任意啓動一個Task就能夠達到解放IIS工做線程的目的,那麼,對於兩個Action,一個執行工做量1的同步操做,一個執行工做量1的同步操做外帶一個工做量1的一步操做,這兩個Action在吞吐以及性能表現上應該相差無幾。代碼以下:asp.net

/// <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

相關文章
相關標籤/搜索