這個案例是這樣的,我有一個 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 意見:
張貼意見