活動對象框架原理
一、概述:
Symbian OS是一個多任務的操作系統,那么為了實現多任務,同時使系統能夠快速響應,高效的進行事件處理,并減輕應用程序員的工作負擔(申請大多數耗時的操作(例如文件系統)由服務提供器來完成,服務提供器完成程序員提交的請求后,將會返回給程序員一個成功或失敗的信號。),Symbian OS特意引入了活動對象的概念。
服務提供器API具有函數的異步和同步版本,供客戶應用程序使用。所謂同步是指,客戶提交請求后,處于等待狀態,等待服務提供器返回成功或失敗的信號后,然后在進行其他操作;所謂異步是指,請求完成,即返回信號之前,調用者也許會繼續執行其他的處理,或者只是簡單的等待。在這里的等待,也可以稱為“阻塞”,信號就是一個事件,我們的代碼就是事件驅動的。為了實現多任務,一般我們使用異步API。
一般操作系統為了實現多任務,往往使用多線程實現,當然,Symbian也是支持多線程的。但是,在同一個線程中運行的活動對象之間進行切換的代價要比線程上下文的切換代價低,這使得對于各種資源比較緊張的Symbian OS來說,使得活動對象更適合事件驅動多任務。
注意:
(1)線程間上下文切換和同一線程的活動對象之間傳遞控制權,在速度上的差別可能會有10倍之差,另外,一個線程大約在內核中有4KB的空間開銷,在用戶空間上有8KB的用于程序棧的空間開銷,而一個活動對象的開銷可能只有幾百字節,甚至更小。
(2)雖然在一個線程內的活動對象是非搶占式地協同運行的,但在它們所在的線程卻是搶占式調度的。
二、概念:
一個活動對象必須派生自基類CActive
class 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、DoCancel()是個純虛函數,必須實現該函數以提供未完成請求所需的功能。但是,需要注意:絕對不應該直接調用該函數,應該總是使用Cancel(),該函數調用DoCancel(),同時確保設置必須的標志,從而表明請求已完成。
3、RunL()是原子操作,當它被活動規劃器調度后,相同線程里,其他任何RunL()都不可以運行,直到這一RunL()完成并返回,因此該方法必須簡短,否則,用戶就會感到等待事件較長,手機好像死機了一樣。
4、如果RunL()異常退出,則調用RunError()(由活動規劃器調用),它為活動對象提供處理自身錯誤的機會。如果能夠處理錯誤,RunError()就應該返回KErrNone;否則,它應該只是返回作為參數傳遞的錯誤碼,在這種情況下,將錯誤傳遞到活動規劃器的Error()函數,默認行為是導致嚴重錯誤。
5、iStatus實際上只是一個封裝的整數,用于表示異步服務提供器返回的狀態或錯誤碼。活動對象發出請求后,服務提供器的第一個任務是將iStatus設為KrequestPending,當請求的服務完成時,服務提供器將iStatus的值設為KErrNone(如果請求成功完成)或錯誤碼。
四、活動對象的實現步驟:
1、構造
活動對象幾乎總是要使用二階段構造,因為活動對象通常需要連接到它們的異步服務提供器,這個連接過程可能失敗。
void CCsvFileLoader::ConstructL (const TDesC& aFileName)
{
iFileName = aFileName;
//RFile iFile
User::LeaveIfError(iFile.open(iFs , iFileName , EFileRead));
//RTimer iTimeWaster
User::LeaveIfError(iTimeWaster.CreateLocal());
CActiveScheduler::Add(this);
//活動對象加到活動規劃器中,這條語句也可以放在第一階段構造函數中
}
選擇最適合活動對象的優先級,該優先級只會影響活動對象在活動規劃器的列表中的順序,實際開發時,很少使用EPriorityStandard以外的優先級。
CCsvFileLoader::CCsvFileLoader(RFs& aFs , CElementList& aElementList , MCsvFileLoaderObserver& aObserver) : CActive (EPriorityStandard)
{
……
}
通過句柄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同時傳遞給兩個服務提供器(因此在相同的活動對象上有多個未完成的請求)
posted on 2008-10-09 20:42
frank.sunny 閱讀(1814)
評論(0) 編輯 收藏 引用 所屬分類:
symbian 開發