來自:http://it.btbctv.cn/n/200609/16/n20060916_61682.shtml
一、概述:Symbian OS是一個多任務的作業系統,那麼為了實現多任務,同時使系統能夠快速響應,高效的進行事件處理,並減輕應用程式員的工作負擔(申請大多數耗時的操作(例如檔案系統)由服務提供器來完成,服務提供器完成程式員提交的請求後,將會返回給程式員一個成功或失敗的
訊號。), Symbian OS特意引入了使用中的物件的概念。服務提供器API具有函數的非同步和同步版本,供客戶應用程式使用。所謂同步是指,客戶提交請求後,處於等待狀態,等待服務提供器返回成功或失敗的訊號後,然後在進行其他動作;所謂非同步是指,請求完成,即返回訊號之前,調用者也許會繼續執行其他的處理,或者只是簡單的等待。在這裡的等待,也可以稱為“阻塞”,訊號就是一個事件,我們的代碼就是事件驅動的。為了實現多任務,一般我們使用非同步API。一般作業系統為了實現多任務,往往使用多線程實現,當然,Symbian也是支援多線程的。但是,在同一個線程中啟動並執行使用中的物件之間進行切換的代價要比線程內容相關的切換代價低,這使得對於各種資源比較緊張的Symbian OS來說,使得使用中的物件更適合事件驅動多任務。
注意:(1)線程間環境切換和同一線程的使用中的物件之間傳遞控制權,在速度上的差別可能會有10倍之差,另外,一個線程大約在核心中有4KB的空間開銷,在使用者空間上有8KB的用於程式棧的空間開銷,而一個使用中的物件的開銷可能只有幾百位元組,甚至更小。(2)雖然在一個線程內的使用中的物件是非搶佔式地協同啟動並執行,但在Symbian中,它們所在的線程確是搶佔式調度的。 二、概念:一個使用中的物件必須派生自基類CActiveclass CActive : public CBase{public: enum Tpriority{ EPriorityIdle = -100; EPriorityLow = -20; EPriorityStandard = 0; EPriorityUserInput = 10; EPriorityHigh = 20;}public: IMPORT_C ~CActive (); IMPORT_C void Cancel ();//刪除未完成請求的函數 …… IMPORT_C void SetPriority (TInt aPriority); Inline TBool IsActive () const; ……protected: IMPORT_C CActive (TInt aPriority); IMPORT_C void SetActive ();
virtual void DoCancel () = 0;//
兩個純虛函數,繼承類必須實現它們
virtual void RunL () = 0;//處理函數 IMPORT_C virtual TInt RunError (TInt aError);public: TrequestStatus iStatus;//代表請求狀態 …….private: TBool iActive; …….}通過上面的CActive聲明可以看出:使用中的物件和線程類似,構造時也會有一個優先順序值來決定它們如何被調度,通常為使用中的物件提供一個標準優先順序EPriorityStandard。當使用中的物件響應的非同步服務完成時,就會產生一個事件。活動調度器會偵測到事件,並決定每個事件對應的是哪個使用中的物件,然後調用恰當的使用中的物件去處理事件。當使用中的物件處理事件時,直到事件處理函數返回到活動調度器,該對象都是無法被搶佔的,
也就是說,
RunL
()事件處理函數是一個原子操作。在Symbian OS中,使用中的物件相互協作並順序的實現多任務,也不需要對共用的資源進行同步保護。另外,因為使用中的物件在同一個線程中運行,所以可以更容易地共用記憶體和對象,儘管使用中的物件存在於同一線程,但它們仍然是各自獨立啟動並執行,這就好像同一個進程中的線程是獨立啟動並執行一樣。 三、關於使用中的物件基類CActive的幾點說明:參照上面CActive的聲明1、 必須在發布請求時調用SetActive(),否則使用中的物件規劃器在搜尋已完成的使用中的物件時忽略它,從而導致錯誤。需要說明的是,在CActive這個基類中,並沒有任何實際的函數用來發布非同步請求,我們自己必須編寫這種函數,通常取名為StartL()。2、 ()是個純虛函數,必須實現該函數以提供未完成請求所需的功能。但是,需要注意:絕對不應該直接調用該函數,應該總是使用Cancel(),該函數調用DoCancel(),同時確保設定必須的標誌,從而表明請求已完成。DoCancel3、 ()是原子操作,當它被活動規劃器調度後,相同線程裡,其他任何RunL()都不可以運行,直到這一RunL()完成並返回,因此
該方法必須簡短,否則,使用者就會感到等待事件較長,手機好像死機了一樣。RunL4、 如果RunL()異常退出,則調用RunError()(由活動規劃器調用),它為使用中的物件提供處理自身錯誤的機會。如果能夠處理錯誤,RunError()就應該返回KErrNone;否則,它應該只是返回作為參數傳遞的錯誤碼,在這種情況下,將錯誤傳遞到活動規劃器的Error()函數,預設行為是導致嚴重錯誤。5、
實際上只是一個封裝的整數,用於表示非同步服務提供器返回的狀態或錯誤碼。使用中的物件發出請求後,
服務提供器的第一個任務是將iStatus設為KrequestPending,當請求的服務完成時,服務提供器將iStatus的值設為KErrNone(如果請求成功完成)或錯誤碼。
iStatus 四、使用中的物件的實現步驟:1、 構造使用中的物件幾乎總是要使用二階段構造,因為使用中的物件通常需要串連到它們的非同步服務提供器,這個串連過程可能失敗。選擇最適合使用中的物件的優先順序,該優先順序只會影響使用中的物件在活動規劃器的列表中的順序,實際開發時,很少使用EPriorityStandard以外的優先順序。CCsvFileLoader::CCsvFileLoader(RFs& aFs , CElementList& aElementList , MCsvFileLoaderObserver& aObserver) : CActive (EPriorityStandard){ }void CCsvFileLoader::ConstructL (const TDesC& aFileName){ iFileName = aFileName; User::LeaveIfError(iFile.open(iFs , iFileName , EFileRead)); User::LeaveIfError(iTimeWaster.CreateLocal()); CActiveScheduler::Add(this);//使用中的物件加到活動規劃器中,這條語句也可以放在第//一階段建構函式中}通過控制代碼RTimer和RFile連結到所需的兩個非同步伺服器。
警告:必須將使用中的物件都添加到活動規劃器中,並且只能添加一次。添加失敗將產生請求迷失的嚴重錯誤。2、 啟動使用中的物件void CCsvFileLoader::Start( ){ TInt delay = (iFileName.Size() % 10) * 100000; iTimeWaster.After(iStatus , delay); SetActive();//將iActive設為Etrue}非同步服務提供器(運行在另一線程或進程中)將通過完成下面兩件事來用訊號通知線程(使用中的物件所屬的線程)。(1) 增加線程的訊號量(這個訊號量,一般情況用不到。只是當使用中的物件所屬的線程被掛起時,通過增加訊號量重新喚醒這個線程,而線程的掛起通過函數WaitForAnyRequest())。(2) 將給定的TRequestStatus設定為不同於KRequestPending的值(如果一切運行良好,則很有可能是KErrNone)。3、 ()RunL這個函數比較複雜,它將執行大量任務:(1) 決定下一次迭代應該做什麼(載入資料或浪費時間)(2) 檢查最後一次迭代狀態,並且將這個狀態報表給觀察器(3) 處理所有載入的資料一般使用中的物件的這個RunL()方法希望完成上述的一項或幾項工作。在這裡還可以再次調用Start(),也就是說可以再次發布服務要求。4、 在RunError()中處理錯誤使用中的物件完全允許RunL()異常退出,結尾是“L”表明了這一點。如果該函數確實異常退出,則它異常退出時的錯誤碼將被傳遞到RunError()。TInt CCsvFileLoader::RunError(TInt aError){ iObserver.NotifyLoadCompleted(aError , *this); return KErrNone;}這裡的錯誤處理很簡單,僅僅將錯誤傳遞給了觀察器。5、 刪除未完成的請求所有的使用中的物件都必須實現一個DoCancel()方法,用於刪除任何未完成的請求。void CCsvFileLoader::DoCancel(){ if(iWastingTime || !iHaveTriedToLoad){ iTimeWaster.Cancel();}}CActive::Cancel調用DoCancel(),絕對不允許重寫CActive::Cancel()自身(它無論如何都不是虛函數),因為該函數完成了大量重要的工作。(1) 檢查使用中的物件是否實際上處於活動狀態。如果不是,它就會返回,而不作任何事情。(2) 調用DoCancel()(3) 等待請求完成,這必須儘可能完成(原始請求可能在刪除它之前就已經完成)(4) 將iActive設為假。注意:可以重寫DoCancel()方法,但不可以重寫Cancel()方法,這個方法會自動調用DoCancel(),另外,我們也不能直接調用DoCancel()方法,這一點也是很重要的。6、 解構函式CCsvFileLoader::~CCsvFileLoader(){ Cancel(); iFile.close(); iTimeWaster.Close();}任何使用中的物件解構函式的第一步都是調用Cancel()刪除任何未完成的請求。如果刪除一個帶有未完成請求的使用中的物件,則產生一個請求迷失的嚴重錯誤。必須關閉非同步服務提供器的任何控制代碼,從而避免資源泄漏。
基CActive
解構函式將自動調用Deque
(),從活動規劃器列表中移除使用中的物件。7、 啟動活動規劃器
UI
架構將自動建立、安裝和啟動活動規劃器。因此,如果不打算編寫.exe
(控制台應用程式或Symbian OS
伺服器)或DLL
(需要顯式啟動活動規劃器),則可以省略這些步驟。
啟動活動規劃器前,必須完成如下步驟:(1) 執行個體化活動規劃器(2) 將其安裝到線程中(3) 建立一個使用中的物件並添加到活動規劃器中(4) 發出一個請求void DoExampleL(){ CActiveScheduler* scheduler = new(ELeave) CActiveScheduler; CleanupStack::PushL(scheduler); CActiveScheduler::Install(scheduler); CElementsEngine* elementEngine = CElementsEngine::NewLC(*console); elementEngine->LoadFromCsvFilesL();//發出請求 CActiveScheduler::Start();//啟動活動規劃器 CleanupStack::PopAndDestroy(2 , scheduler);} 五、常見的使用中的物件錯誤:1、 啟動使用中的物件前忘記調用CActiveScheduler::Add()2、 在發布或重新發布非同步請求時沒有調用SetActive()3、 將相同的iStatus同時傳遞給兩個服務提供器(因此在相同的使用中的物件上有多個未完成的請求)