多線程編程的探索與實踐(圖)

    在程序開發中,多線程是比較普遍的應用。還記得剛從事程序開發時,多線程對我來說是很神祕和充滿嚮往的,如同霧裏看花不知其要領和實質。由於工作中所參與的這個項目是基於多線程和多用戶的,在經過一番歷煉後對多線程有了一些拙見,特記錄下來。
項目中沒有直接使用開發環境所提供的多線程類庫,而自己封裝這些功能以提供更靈活便捷的編程方法。這裏面基於一些概念:
    任務巢:多個任務的集合,提供任務包先進先出的隊列管理。
    任務包:具體執行某個特定操作的任務,封裝了要執行操作所需的若干參數、回調函數等。
    投遞:打好任務包後,將任務包傳遞到任務巢排隊執行。
    同步調用:調用及被調用者在同一個線程執行,在同一時刻只能執行其一。異步調用則反之。
    在看一下投遞任務包及進行異步操作是怎樣實現的:
    步驟: 創建任務包-->封裝參數-->投遞任務-->啓動異步操作-->執行回調函數,發回執行結果。

    上述的機制可用下圖來描述:

具體實現代碼如下:
(代碼段1)
IInvokePackage ip = (IInvokePackage)SendSysMsg(new CMsg(MTSystemService.CreateInvokePackage, false));  //創建任務包
if (null != ip) {
ip.TargetObject = this;
ip.TargetMethod = new InvokePackageHandler1(SureLoadProp);  //啓動異步後要執行的操作
Debug.Assert(null != ip.TargetMethod);

ip.Parameters.Add(obj);  //封裝參數
ip.Parameters.Add(strPropName);
ip.UserState = obj;
ip.Callback = new AsyncCallback(this.OnSureLoadPropCallback);  //回調函數
IAsyncResult ar = (IAsyncResult)SendSysMsg(new CMsg(MTSystemService.BeginInvoke, ip, this.m_PlugInMgr.DBJobNest));  //投遞任務包,觸發異步調用
    上述操作就是實現異步操作的整個過程,最後任務包對象ip將被投遞到任務巢按先進先出的原則排隊執行,最終將會在另一個線程中執行函數SureLoadProp,執行完畢,回調函數this.OnSureLoadPropCallback將會被調用:
(代碼段2)
private void OnSureLoadPropCallback(IAsyncResult ar){
   ArrayList al = new ArrayList(1);
   object Result = SendSysMsg(new CMsg(MTSystemService.EndInvoke,ar,al));//請求執行結果

   IInvokePackage ip = (IInvokePackage)al[0];

   bool bRet = false;
   if(Result is Exception){
    AppGlobal.g.LogError((Exception)Result);
    bRet = false;
   }
   else{
    bRet = Result is ArrayList;
   }
   
   PostSysMsg(new CMsg(MTAppPatientMgrPlugIn.QueryPatientsResult, Result, ar, bRet));//發回操作結果
  }
    在上述函數中,首先通過消息向系統請求指定異步操作的執行結果,此時如果該任務還未執行完,此處將會等待直至執行完。然後通過消息QueryPatientsResult將執行結果Result對象返回給調用者。
    再回頭看一下這裏面的機制:
    代碼段1的操作是在當前線程中(比如主線程)執行的,當任務包投遞出去後就不用管了,當前線程可繼續進行接下來的操作,直到接受任務包的任務巢(已經是另外一個線程)執行完畢通知回來再獲取操作結果。這樣做的好處就是,當要執行時間過長消耗性能較大的操作時,如果不用另外的線程來做,那麼當前線程就要等待這個操作的完成,才能執行其他操作,這時就會出現某些持續的操作停頓,用戶的操作也要受到影響。那麼此時,將該任務交給另一個線程來做,當前線程的其他的操作不受影響,用戶的操作也會很流暢。
當任務包投遞出去後,並不能保證馬上執行,因爲接受此任務包的任務巢可能有多個任務包,它們是逐一執行的,剛投遞的任務只有等待其他任務包執行完成後才能執行。雖然這樣能確保在此任務巢中的任務能同步執行,但與其他任務巢(線程中的操作)不能同步,所以如果它們要訪問同一資源時,就可能會出現讀寫迸發操作,造成數據不一致。這時可用鎖定機制,這方面的應用在以前的文章中敘述過。
  

    那麼如何將任務包投遞到特定的任務巢呢,看代碼1中的最後一句代碼:
IAsyncResult ar = (IAsyncResult)SendSysMsg(new CMsg(MTSystemService.BeginInvoke, ip, this.m_PlugInMgr.DBJobNest));此時投遞任務包是指定了任務巢this.m_PlugInMgr.DBJobNest,此任務巢是專門用作數據庫操作的。如不指定,將會被投遞到默認任務巢。
    總結:
    1. 在主線程中啓動異步操作後,根據需要可在異步線程裏面再次下一層異步操作。雖然異步操作增加程序執行的效率,但同時會增加程序的複雜度和管理難度。所以異步操作不是越多層次越好,在最大性能的前提下應儘量減少異步調度嵌套次數。
    2. 由於線程的調度幾乎是在無聲無息地進行,一旦出現多線程的問題將很難較直觀地發現問題,此時必須要很清楚線程調度的思路,並在關鍵的地方輸出如線程ID等信息,程序中隱晦的問題纔會逐漸明朗,這是解決多線程問題的關鍵步驟。 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章