活動規劃器 (Active Scheduler)
由于使用多線程來處理異步請求比較消耗系統資源,所以symbian 使用了活動對象(Active Object)來解決異步請求的問題。
活動規劃器(active scheduler)用于處理由活動對象提出的異步請求。它檢測活動對象提出的異步請求,并安排活動對象的請求完成事件的執行順序。活動規劃器僅用一個事件處理線程來規劃各個活動對象提出的事件請求,所以它要比多線程實現異步請求占用更少的資源。
一個活動規劃器通常對應到一個線程,CActiveScheduler 類的靜態方法通常都是針對當前線程中對象的操作。
在程序中使用活動對象時,首先應該創建一個活動規劃器對象,并把它安裝到當前線程,也就是把活動規劃器和當前線程關聯。 CActiveScheduler::Install()方法可以把活動規劃器安裝到當前線程, CActiveScheduler::Current() 方法可以獲取和當前線程關聯的活動規劃器。如:
CActiveScheduler::Install(CAtv) CActiveScheduler* sco2 =CActiveScheduler::Current(); |
Add()方法可以把活動對象添加到活動規劃器中,這樣活動規劃器就可以處理活動對象所提出的異步請求,當異步請求完成事件發生時調用活動對象的RunL()方法。
活動規劃器的 Add() 方法把指定的活動對象添加到當前的活動規劃器中。但是在某些時候可能需要刪除在活動規劃器中的活動對象:如果活動對象被銷毀,那么同時活動對象就在活動規劃器中被刪除了,另外使用活動規劃器的Deque()方法也可以把活動對象從活動規劃器中刪除。
在調用Add()方法之前必須確認活動規劃器已經安裝到當前線程,否則將發生E32USER-CBase 44 panic。Add()方法包括一個CActive型的參數, 這是一個指向活動對象的指針,在調用Add()方法時,這個指針不可以為 NULL,否則函數發生 E32USER-CBase 48 panic。另外與要注意的是,在使用Add()方法之前確認活動對象是否已經添加到活動規劃器中,如果這個對象已經添加到活動規劃器中那么將發生E32USER-CBase 41 panic。
CActiveScheduler::Start()方法啟動一個在當前活動規劃器控制下的的循環等待。而CActiveScheduler::Stop()則可以終止活動規劃器的循環等待。活動規劃器實際上是構建了一個事件處理的框架,它在循環中使用User::WaitForAnyRequest()等待活動對象的異步請求,當請求事件完成時,就調用一該請求事件相關的活動對象的RunL()方法。活動對象調用RunL()時把它放在TRAP下,這樣即使RunL()函數發生Leave活動對象也有可以對它處理,執行一些必要的內存清理。當RunL()發生異常退出之后,首先進入活動對象的RunError()方法,這個方法是一個需方法,有一個默認實現,并返回一個整型的值,如果返回的整形值不是KEeeNone,那么活動規劃器將調用自己的Error()方法。Error()方法也有一個默認的實現:發生一個E32USER-CBase 47 panic。
活動對象(Active Object)
CActive類是活動對象(Active Object)的核心類,所有的活動對象類應該從這個類繼承。在CActive 中,封裝了兩個功能:一個是向異步服務提供者發出異步請求;另外一個是處理請完成事件。一個應用程序中可以包含若干個活動對象,并且是它們的都在活動規劃器(active scheduler)的控制下運行。
活動對象應該包括一個和請求函數相關連的請求對象,請求函數是專門用于完成異步請求的,由于帶有請求函數的對象提供了異步服務,所以在SDK的文檔里又把他們叫做異步服務提供者(asynchronous service provider)。這里可以舉一個簡單的例子,RTimer中包括了一些請求函數,如After()、At()等。所以RTimer型的對象是一個異步服務提供者。所以在活動對象中,通常會有一個服務提供者成員,它的作用是提出一個異步請求,并異步完成他們的請求。
活動對象的 RunL() 方法用于處理活動對象的請求完成事件。活動對象類作為CActive類的派生類,它必須實現RunL()方法。如果有業務邏輯需要,RunL()可以發出另外一個新的異步請求。當請求結束事件發生時,這個函數被活動規劃器調用,也就是說,在活動規劃器的WaitForAnyRequest()函數結束以后RunL()函數被調用。WaitForAnyRequest()實際上是對User::WaitForAnyRequest()的調用。
在調用活動對象的RunL方法前,活動規劃器需要確定,哪一個活動對象擁有最高優先權,然后標記該對象的請求作為請求完成事件。這個優先權是在活動對象構造的時候指定的。RunL運行在活動規劃器所設定的TRAP中,如果RunL在執行過程中發生leave那么,那么活動規劃器將調用RunError()方法。一旦調用了活動規劃器的 Start方法,那么所有的用戶代碼將運行在活動規劃器的某個活動對象的RunL()函數或RunError函數下。
活動對象 RunError() 方法用于處理請求結完成事件處理函數RunL() 的異常退出(Leave)。如果活動對象的RunL()函數發生Leave,那么活動規劃器將調用活動對象的RunError方法。這樣這個發生錯誤的活動對象就可以完成一些必要的內存清理。如果有派生類實現了這個方法,那么就應該對可能的 leave 做出處理,并返回KErrNone;如果返回了其他的錯誤碼,那么CActiveScheduler類的Error()將被調用。
RunError() 方法的默認實現返回一個異常退出碼,而一旦發生leave這個異常退出碼是一個非KErrNone的值,這樣可以認為:一般情況下,活動對象的RunL()方法如果發生leave,那么CActiveScheduler類的Error()將被調用。
注意:如果活動對象用于錯誤處理,那么在他的派生類中應該提供一個比較好的CActiveScheduler Error函數,以便處理那些錯誤。
活動對象的SetActive()方法用于向活動規劃器提交一個異步請求。派生類應該在請求發出以后調用這個方法。活動對象也可以通過一定的方法來取消請求,但是這里需要說明的是,取消請求只是讓請求提前完成。關于異步請求取消可以使用Cancel()方法。
請求狀態(TRequestStatus)
服務提供者為了完成服務,需要有一個如何完成服務的信息。TRequestStatus 類的對象用于存放為服務提供者定制的請求完成狀態。當線程提出請求時,例如RTimer 的After()請求,請求狀態對象被作為參數傳遞到請求函數中。服務完成以后,服務提供者發送信號給服務請求對象的請求信號量并在請求對象中保存一個完成碼,通常這個完成碼的值是KErrNone或者是其他系統定義的錯誤碼。TRequestStatus 類是密封類,不再被繼承。
活動規劃器和活動對象協同工作
這里的代碼和附件中的例子一致,可以下載例子直接在VC6++中調試。活動規劃器和活動對象的的協同工作過程如下。首先應該創建一個與當前線程相關的活動規劃器:
CATV* sco = new(ELeave) CATV(); // 推入清理棧 CleanupStack::PushL(sco); // 把 sco 活動規劃器安裝為當前線程的活動對象 // 安裝以后,sco 活動對象專門處理當前線程的請求事件 CActiveScheduler::Install(sco); |
接著創建一個活動對象,TmCount類是一個從CActive類派生的活動對象類。
TmCount* timeCount = TmCount::NewLC(); |
然后可以從NewLC()方法中看到活動對象構造的具體細節:
TmCount* TmCount::NewLC() { console->Printf(_L("NewLC\n")); // 使用 C++默認構造函數構造 TmCount* result = new (ELeave) TmCount(); // 把活動對象推入清理粘 CleanupStack::PushL( result ); // 調用兩階段構造函數,構造活動對象內部成員 result->ConstructL(); // 返回活動對象指針 return result; }; |
在這個默認的構造函數中,還調用了默認的C++構造函數,這個比較關鍵,因為正是在這個構造函數中,把活動對象添加到了活動對象,在這個;函數中,還可以定義活動對象的優先權限:
TmCount::TmCount():CActive(0) // 這里可以設置活動對象的優先級 { // 把自己加入活動規劃器 iTimeCounter=1; CActiveScheduler::Add(this);
};
在ConstructL()中,則獲得了一個記時器句柄 void TmCount::ConstructL() { // 初始化計數器和定時器 iTimeCounter = 0; User::LeaveIfError(iTimer.CreateLocal()); console->Printf(_L("ConstructL\n")); };
|
這樣,活動對象構造完成。接著,程序調用活動對象的請求函數。
在活動對象的請求函數中:
// 活動對象的請求函數 void TmCount::StartL(TInt mT) { // 將請求傳遞給異步服務提供者, // 異步服務提供者,調用請求函數, // 這里需要注意iStatus 這個參數,它是一個請求狀態對象。 // 設定定時器狀態為每隔mTime秒鐘狀態完成一次 iTimer.After(iStatus, 10000 * 100 * mT); // 在調用異步請求函數之后調用SteActive()方法,提交異步請求 SetActive(); }; |
異步服務提供者處理請求,當請求處理完成以后,它生成一個事件。活動規劃器不斷的檢查是否有請求完成(是否有請求完成事件發生),如果有請求完成,那么調用與該請求相關聯的活動對象的RunL()方法:
void TmCountTwo::RunL() { // 計數器+1以后繼續提交延時請求事件 console->Printf(_L("[2]The Count is ->>%d\n"), iTimeCounter++); // 再次提出異步請求 StartL(1); }; |
在這個方法中又提出了一次異步請求,并使計數器值增加。如果條件符合則可以不再讓活動對象提出異步請求,這是一個修改后的RunL():
void TmCount::RunL() { // 計數器+1以后繼續提交延時請求事件 console->Printf(_L("[1]The Count is ->>%d\n"), iTimeCounter++); // 當記數器值大于等于10時,不再提出異步請求。 if (iTimeCounter<10) { StartL(1); } else { //取消請求 Cancel(); } //User::Leave(9); }; |
如果RunL()在運行過程中發生錯誤,則RunError()被調用,以下是RunEror()方法:
TInt TmCount::RunError(TInt aError) { console->Printf(_L("[1]Error\n")); //console->Getch(); return 7; };
如果RunError()方法返回了一個非KErrNone的值那么活動規劃器的Error方法將被調用。 class CATV :public CActiveScheduler { public : IMPORT_C void Error(TInt anError) const;
};
void CATV::Error(TInt anError) const { console->Printf(_L("Error:%d\n"),anError); //CATV::Stop(); };
|