Jen

ASP.NET Async 非同步處理的故事

這個案例是這樣的,我有一個 action,裏面循序處理了3個 remote procedure call,其中1個是Call Bing 的 Web Service,其他2個都是內部 Rest-Like Web Service,都是分散於其他主機的,照一般循序處理的方式,會得到以下結果:
cost = t(Bing Serv) + t(apacheServ) + t(Search Serv)
如果使用非同步的作法,會得到以下結果。
cost = max( t(BingServ) , max( t(apacheServ) ,  t(SearchServ) ) )
註:
t() : 時間
max() : 最大值
cost : 總花費時間
BingServ : Bing Web Service
apacheServ : apache Web Service
SearchServ : play search Web Service

其實概念非常簡單,公司丟了3個案子叫小賴一天完成,小賴1天就依序把3個案子幹完了,但結果很慘,小賴的肝爆了,因為肝爆了,沒得操了,公司就解雇了小賴(fire!!),從此過著什麼樣的生活,我們也不想知道.......後來公司找到了小林,也一次丟了3個案子給小林要他一天做完,小林不想自已做,他把3個案子分出去給手下做,也因為3個案子之間沒有任何關係,所以就同時進行著,小林就一個人跑去樓下枯滴買大杯文山菁茶少冰不加糖,老闆娘看小林可愛還加送珍珠,加量不加價。結果,其中2個案子中午就完成了,而另外一個到下午2點也完成了,小林就馬上去跟公司報告,我完成了3個專案! bravo!

話說回來為什麼要這樣做? 值得嗎?

非同步的做法在 Web ap 開發裏,不是很常用得,很多時候是根本沒必要做。那從這個案例來看,這 3 個 Task 是會造成所謂的 I/O bound (也就是CPU效能會比較好),簡單說就是不會有太多複雜的 CPU 運算,大部分的時間是花在資料的傳輸及等待資料回應上(Network I/O),而上面3個 Task 說到底就只是在做http web request ,實際運算都是分出去到其他主機,而3個task之間更沒有任何血緣關係,不需要等誰的結果出來才能繼續執行,如果讓3 個 Task 「幾乎」同時「開始」執行任務就可節省很多等待的時間。

同步的作法

我們可以看到Worker Thread被連續的3個 Task block 住。








如果改成非同步呼叫,紅條代表時間,是不是短很多 !?
由Worker Thread產生3個 Child Thread 分別處理3個Task













而Worker Thread的定位大概是這樣的,.Net Framework 管理著一個 Threads pool,每當有一個 Request 進來時,就會有一個Worker Thread 被叫去處理,當request 多過 Worker thread之後,就會被queue住,被queue住的數量如果再超過預設的限制,iis 就會回應 503給 client,而這些數量的設定都放在.Net Framework 的 machine.config裏面的processModel區段,有興趣再去看看吧,我是沒什麼興趣。

那在 ASP.NET MVC 的專案中怎麼做到呢? MVC Framework 有提供 AsyncController  可以輕易達到非同步呼叫的實作,實作方法 google 一下就有了,所以今天就沒有要討論這東西,還有別的方式可以達到一樣的效果。(其實 MVC Framework也是用同樣的 Pattern 去實作)

假設我們有一個 hbaseService類別,是 call 遠端 hbase 的 web service,你要給一個關鍵字,然後讓它去搜尋,它長得像這樣:
public class hbaseService
{
    public string query { get; set; }
    public hbaseService(string _query)
    {
        this.query = _query;
    }
        
    //do remoting work
    public string Search()
    {
        //do searching....and return 
        return "hbase search result...";
    }
}
在Controller我們可能會這樣使用它:
public class SearchController : Controller
{
    // GET: /Search/Sync
    public ActionResult Sync(string query)
    {
        hbaseService hbs = new hbaseService(query);
        string result = hbs.Search();
        return View(result);
    }
}

上面所有的處理都跑在 Worker Thread 裏面,如果只有一個 RPC 根本沒差,今天有 3 個 RPC 那可就有差了,那要如何無痛(這裏的無痛指的是不用改動原本的程式)的讓3 RPC Task 不會 Block 住 Worker Thread 呢?  首先幫hbaseService建立一個 AsyncRunner:
public class AsyncRunner
{
    //用來處理 call back 的 hander
    public delegate void hbaseHandler(object sender,string args);
    public event hbaseHandler SearchComplete;

    private hbaseService _s;
    public AsyncRunner(hbaseService s)
    {
        _s = s;
    }
    public void AsyncSearch()
    {
        Thread oThread = new Thread(new ThreadStart(this.run));
        oThread.Start();
    }
    private void run()
    {
        string _result = _s.Search();
        SearchComplete.Invoke(this, _result);
    }
}

到目前為止,我們都沒有動到原本就寫好的 hbaseService 對吧,接著在 Controller 這樣用它:
public class SearchController : Controller
{
    //用在計算有幾用 child thread 被產生,
    //跟AsyncManager.OutstandingOperations.Increment(3)一樣功用
    public int threadCount = 1;
    public object lock_obj = new object();

    // GET: /Search/Async
    public ActionResult Async(string query)
    {
        hbaseService hbs = new hbaseService(query);
        AsyncRunner asyncRunner = new AsyncRunner(hbs);
        asyncRunner.SearchComplete += (sender, args) => {
            //可能有多執行緒同步的問題
            lock (lock_obj)
            {
               ViewData["result"] = args;
            }
            threadCount--;
        };
        asyncRunner.AsyncSearch();
        while (threadCount != 0)
        {
            // Worker Thread do something
            Thread.Sleep(100);
        }
        return View();
    }
}

這樣就算多個 RPC Task 也不會 Block 住 Main Thread 了,是不是呢? 其實就只是把寫桌面應用程式的觀念搬到 Web ,也沒什麼特別的,又是一篇灌水文。

不過實際的案子上我也不是這樣寫的,根本是來亂的嘛我 : ) ,我怎麼好意思亂獻醜呢.

後記:後來小林的役期滿了,就回鄉下種田,再也不寫程式了;至於小賴呢...似乎沒人想起他...

0 意見: