Asynchronous Functions và Active Objects

Symbian-Mobile-Phone-OS-Goes-Open-Source_1

Active Objects là một trong những tinh túy của Symbian OS. Cơ chế này giúp cho việc gọi các hàm bất đồng bộ (Asynchronous Functions) trở nên rất hiệu quả và do đó tiết kiệm năng lượng cho thiết bị di động.

Mặc dù trong Symbian OS cho phép tạo nhiều Thread, nhưng việc này vốn khá nguy hiểm (kể cả trên các nền tảng khác), cần thiết lắm ta mới phải dùng, và phải dùng cẩn thận. Một lựa chọn phổ biến là hãy để chương trình chạy trên một Main Thread duy nhất, hướng sự kiện (event-driven) bằng cách dùng Asynchronous Functions và Active Objects.

Trước hết chúng ta tìm hiểu sơ qua thế nào là Synchronous/Asynchronous Functions.

Synchronous Functions (truyền thống) chỉ được return khi và chỉ khi nó hoàn thành hết công việc của nó, tức là nơi gọi nó sẽ dừng lại chừng nào nó return thì mới chạy tiếp. Nên nếu hàm này chạy khá lâu (mà thường là lâu trên các phone cùi), chương trình sẽ khựng lại.

Asynchronous Functions thì lại khác, nó được return ngay lập tức, công việc nó thực sự cần làm sẽ được chạy song song trên một Thread khác, hay thậm chí (rất hay gặp trong Symbian) là trên một Process khác. Khi nào xong việc, Thread hay Process đó sẽ gửi tín hiệu về cho Thread cũ (nơi gọi) kèm theo kết quả công việc.

Khi lập trình Symbian, chúng ta rất thường xuyên dùng Active Objects để gọi các Asynchronous Functions của thư viện hệ thống. Để làm việc tốt, cần phải hiểu rõ bản chất hoạt động của chúng, thành thạo cách cài đặt và nắm bắt một vài lỗi thường gặp.

Dưới đây là bức tranh khái quát về những gì chúng ta sẽ bàn luận:

Steve Babin, 2006, Developing Software for Symbian OS, p.238
Image source: Steve Babin, 2006, Developing Software for Symbian OS, p.238

CActiveScheduler là lớp của thư viện hệ thống, mỗi một Thread chỉ được phép có duy nhất một instance của CActiveScheduler được install vào (nếu ta cố tình tạo ra instance thứ hai và install, chương trình sẽ sinh ra lỗi runtime). GUI application luôn có sẵn một CActiveScheduler bởi vì bản thân GUI application cũng sử dụng CActiveScheduler để xử lý các sự kiện GUI. Vậy nên nếu bạn đang viết GUI application thì không cần quan tâm đến việc tạo CActiveScheduler, chỉ đơn giản dùng nó qua các hàm static.

CActiveScheduler chứa danh sách các Active Objects. Mỗi một Active Object có một biến thành viên tên iStatus kiểu TRequestStatus. TRequestStatus thực chất là lớp bao, trong nó đơn giản chứa 1 biến kiểu int. iStatus dùng để kiểm tra tình trạng của việc gọi Asynchronous Functions.

Tất cả các Asynchronous Functions đều được gọi bên trong Active Objects. Khi gọi một Asynchronous Function F() bên trong một hàm M() của Active Object A, chúng ta phải truyền tham chiếu của iStatus cho F() (trong danh sách các parameters của F() luôn có một parameter kiểu TRequestStatus&). Sau đó gọi hàm SetActive() của A để đánh dấu A được “active”. Hàm F() tự động gán giá trị cho iStatus = KRequestPending (một hằng số int), sau khi công việc của F() kết thúc ở một Thread hoặc Process khác, iStatus sẽ được gán giá trị khác để cho biết kết quả.

Chúng ta lấy kết quả ở trong hàm RunL() của A, RunL() là một callback được gọi bởi CActiveScheduler khi F() đã hoàn thành nhiệm vụ. Dĩ nhiên, trong RunL() ta có quyền kiểm tra iStatus bằng bao nhiêu, nếu bằng KErrNone, thì là thành công, còn khác đi là có lỗi, mã lỗi bằng iStatus.

Bạn có thắc mắc tại sao một GUI application chạy lên nó nằm đó mãi, khi nào nhấn exit nó mới thoát không? Đó là vì CActiveScheduler cài đặt một vòng lặp vô tận, trong mỗi lần lặp, nó làm các công việc sau:

  1.  Sử dụng “Request Semaphore” của Main Thread để chờ đợi tín hiệu (signal) từ các Asynchronous Functions ở Thread/Process khác xem có function nào kết thúc chưa. Nếu chưa thì cứ block lại ở đó, nếu có thì sang bước thứ 2.
  2. Duyệt qua danh sách các Active Objects, xem có object nào “active” và iStatus khác KRequestPending thì gọi hàm RunL() của nó. Xong trở lại bước 1.

Chú ý: “Request Semaphore” là một Semaphore được Symbian OS tự tạo cho mỗi Thread chuyên dành cho CActiveScheduler dùng. Cụ thể Semaphore sẽ được trình bày trong bài khác nói về Thread trên Symbian OS.

Để bắt đầu vòng lặp trên, gọi CActiveScheduler::Start(), muốn kết thúc vòng lặp, bên trong một hàm RunL() nào đó, gọi CActiveScheduler::Stop(). GUI application tự gọi các hàm Start() và Stop() này.

Ta thử viết một Active Object có tên CWifiConnector, nhiệm vụ: kết nối Wifi bất đồng bộ.

Cho CWifiConnector kế thừa lớp CActive (CActive là lớp của thư viện hệ thống), bắt buộc phải cài đặt 2 hàm của CActive là RunL() và DoCancel().

// File: WifiConnector.h
#include

class CWifiConnector : public CActive
{
public:
	// Constructor 2 pha để tránh memory leak
	static CWifiConnector* NewL();
	~CWifiConnector();

	void Start(); // Bắt đầu connect Wifi
	void Stop(); // Dừng lại việc connect

	bool IsRunning(); // Kiểm tra xem connect xong chưa
	bool Result(); // Lấy kết quả connect

protected:
	// Đến từ CActive
	void RunL(); // Callback gọi khi connect kết thúc
	void DoCancel(); // Làm gì để dừng lại việc connect

private:
	CWifiConnector();
	void ConstructL();

	// 2 biến này sẽ thực hiện việc connect Wifi bất đồng bộ
	RSocketServ iSocketServ;
	RConnection iConnection;

	// Biến này hứng về kết quả connect
	bool iResult;
};

// File: WifiConnector.cpp

CWifiConnector* CWifiConnector::NewL()
{
	// Constructor 2 pha
	CWifiConnector* self = new (ELeave) CWifiConnector();
	CleanupStack::PushL(self);
	self->ConstructL();
	CleanupStack::Pop(self);
	return self;
}

CWifiConnector::CWifiConnector() :
		// Mỗi Active Object có 1 độ ưu tiên, càng cao thì CActiveScheduler
		// sẽ càng ưu tiên gọi RunL() của nó trước.
		// Tuy nhiên thường dùng độ ưu tiên EPriorityStandard
		// Thiếu dòng này là 1 lỗi thường gặp, nhưng là lỗi compile
		CActive(CActive::EPriorityStandard)
{
	// Thêm Active Object này vào danh sách của CActiveScheduler
	// Thiếu dòng này là 1 lỗi thường gặp
	CActiveScheduler::Add(this);
}

void CWifiConnector::ConstructL()
{
	// Các hàm này của thư viện hệ thống

	User::LeaveIfError( iSocketServ.Connect() );

	// Phòng khi sau dòng này có thể Leave thì
	// iSocketServ vẫn được Close() đàng hoàng
	CleanupClosePushL( iSocketServ );

	User::LeaveIfError( iConnection.Open(iSocketServ) );
}

CWifiConnector::~CWifiConnector()
{
	Stop();
	iSocketServ.Close();
	iConnection.Close();
}

void CWifiConnector::Start()
{
	// Nếu đã được đánh dấu là "active", tức là đang có connect mà chưa xong,
	// thì không làm gì hết
	// IsActive() là hàm của lớp cha: CActive

	// Thiếu dòng này là 1 lỗi thường gặp
	// Nếu đang active mà SetActive() tiếp thì chết
	if (IsActive()) return;

	iResult = false;
	iConnection.Start(iStatus); // Gọi hàm bất đồng bộ để connect

	// Đánh dấu "active"
	// SetActive() là hàm của lớp cha: CActive
	// Thiếu dòng này là 1 lỗi thường gặp
	// Không active lên thì lấy gì gọi RunL()
	SetActive();
}

void CWifiConnector::RunL()
{
	if (iStatus == KErrNone) // Tốt, không có lỗi
	{
		iResult = true; // Connect Wifi thành công tốt đẹp
	}
	else // Có lỗi
	{
		iResult = false;
	}
}

void CWifiConnector::DoCancel()
{
	// Thôi, không connect nữa, RunL() sẽ không được gọi
	iConnection.Stop();
}

void CWifiConnector::Stop()
{
	// Cancel() là hàm của lớp cha: CActive
	// Trong Cancel(), nó kiểm tra nếu đang "active" thì mới gọi DoCancel()
	// Cho nên ta đừng bao giờ trực tiếp gọi DoCancel()
	// Mà chỉ gọi Cancel() như thế này:
	Cancel();
}

bool CWifiConnector::IsRunning()
{
	return IsActive();
}

bool CWifiConnector::Result()
{
	return iResult;
}

Sử dụng CWifiConnector:

tạo instance: CWifiConnector* connector = CWifiConnector::NewL();

connect Wifi: connector->Start();

ở một nơi nào đó:

if (!connector->IsRunning())
{
	if (connector->Result())
	{
		printf("Success");
	}
	else
	{
		printf("Error");
	}
}

Dừng connect: connector->Stop();

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s