Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Symbian OS Explained - Effective C++ Programming For Smartphones (2005) [eng].pdf
Скачиваний:
60
Добавлен:
16.08.2013
Размер:
2.62 Mб
Скачать

THREAD PRIORITIES

155

(that is, a handle that is in the thread handle list) to pass between threads in a process, you need to duplicate it using RThread::Duplicate():

RThread properhandle.Duplicate(RThread());

You can acquire a handle to a different thread either by creating it or by opening a handle on a thread which currently exists in the system. As you can see from its definition, class RThread defines several functions for thread creation. Each function takes a descriptor representing a unique name for the new thread, a pointer to a function in which execution starts, a pointer to data to be passed to that function and a value for the stack size of the thread, which defaults to 8 KB. The Create() function is overloaded to allow you to set various options associated with the thread’s heap, such as its maximum and minimum size and whether it shares the creating thread’s heap or uses a specific heap. By default on Symbian OS, each thread has its own independent heap as well as its own stack. The size of the stack is limited to the size you set in RThread::Create(), but the heap can grow from its minimum size up to a maximum size, which is why both values may be specified in one of the overloads of Create().3 Where the thread has its own heap, the stack and the heap are located in the same chunk of memory.

When the thread is created, it is assigned a unique thread identity, which is returned by the Id() function of RThread as a TThreadId object. If you know the identity of an existing thread, you can pass it to RThread::Open() to open a handle to that thread. Alternatively, you can use an overload of the Open() function to pass the unique name of a thread on which to open a handle. The thread’s name is set as a parameter of the Create() function overloads when the thread is created; once you have a handle to a thread, it is possible to rename it using RThread::Rename() if the thread is not protected.

RThread has been modified in releases of Symbian OS v8.0 to protect threads against abuse from potentially malicious code.

10.2 Thread Priorities

On Symbian OS, threads are pre-emptively scheduled and the currently running thread is the highest priority thread ready to run. If there are two

3 You can also specify the minimum and maximum heap size for the main thread of a component that runs as a separate process in its .mmp file, using the following statement:

epocheapsize minSizeInBytes maxSizeInBytes

156

SYMBIAN OS THREADS AND PROCESSES

or more threads with equal priority, they are time-sliced on a round-robin basis. The priority of a thread is a number: the higher the value, the higher the priority.

When writing multithreaded code, you should consider the relative priorities of your threads carefully. You should not arbitrarily assign a thread a high priority, otherwise it may pre-empt other threads in the system and affect their response times. Even those threads which may legitimately be assigned high priorities must endeavor to make their event-handling service complete rapidly, so they can yield the system and allow other threads to execute.

A thread has an absolute priority which is calculated from the priority assigned to the thread by a call to RThread::SetPriority() and optionally combined with the priority assigned to the process in which it runs. The TThreadPriority and TProcessPriority enumerations, taken from e32std.h, are shown below.

enum TThreadPriority

{

EPriorityNull=(-30),

EPriorityMuchLess=(-20),

EPriorityLess=(-10),

EPriorityNormal=0,

EPriorityMore=10,

EPriorityMuchMore=20,

EPriorityRealTime=30,

EPriorityAbsoluteVeryLow=100,

EPriorityAbsoluteLow=200,

EPriorityAbsoluteBackground=300,

EPriorityAbsoluteForeground=400,

EPriorityAbsoluteHigh=500

};

enum TProcessPriority

{

EPriorityLow=150,

EPriorityBackground=250,

EPriorityForeground=350,

EPriorityHigh=450,

EPriorityWindowServer=650,

EPriorityFileServer=750,

EPriorityRealTimeServer=850,

EPrioritySupervisor=950

};

The following values are relative thread priorities: EPriorityMuchLess, EPriorityLess, EPriorityNormal, EPriorityMore and EPriorityMuchMore. If these values are passed to RThread::SetPriority(), the resulting absolute priority of the thread is the combination of the priority of the process and the relative value specified. For example, if the process has TProcessPriority::EPriorityHigh (=450), and SetPriority() is called with TThreadPriority of

EPriorityMuchLess, the absolute priority of the thread will be

STOPPING A RUNNING THREAD

157

450 − 20 = 430. If, for the same process, TThreadPriority:: EPriorityMuchMore is passed to SetPriority() instead, the absolute priority of the thread will be 450 + 20 = 470.

The remaining TThreadPriority values, except EPriorityRealTime,4 are absolute priorities that allow the priority of the thread to be independent of the process in which it is running. If these values are passed to a call to RThread::SetPriority(), the priority of the thread is set to the value specified, and the process priority is ignored. For example, if the process has TProcessPriority::EPriorityHigh (=450), the absolute priority of the thread can range from EPriorityAbsoluteVeryLow (=100) to EPriorityAbsoluteVeryHigh (=500), depending on the absolute

TThreadPriority value selected.

All threads are created with priority EPriorityNormal by default. When a thread is created it is initially put into a suspended state and does not begin to run until Resume() is called on its handle. This allows the priority of the thread to be changed by a call to SetPriority() before it starts to run, although the priority of a thread can also be changed at any time.

The secure RThread class in EKA2 does not allow you to call SetPriority() on a handle to any thread except that in which the code is currently running. This prevents a badly programmed or malicious thread from modifying the priorities of other threads in the system, which may have a serious effect on the overall system performance.

10.3 Stopping a Running Thread

A running thread can be removed from the scheduler’s ready-to-run queue by a call to Suspend() on its thread handle. It still exists, however, and can be scheduled to run again by a call to Resume(). A thread can be ended permanently by a call to Kill() or Terminate(), both of which take an integer parameter representing the exit reason. You should call Kill() or Terminate() to stop a thread normally, reserving Panic() for stopping the thread to highlight a programming error. If the main thread in a process is ended by any of these methods, the process terminates too.

On EKA1, a thread must call SetProtected() to prevent other threads from acquiring a handle and stopping it by calling Suspend(), Panic(), Kill() or Terminate(). On EKA2, the security model ensures that a thread is always protected and the redundant SetProtected() method has been removed. This default protection ensures

4 TThreadPriority::EPriorityRealTime is treated slightly differently. Passing this value to SetPriority() causes the thread priority to be set to TProcessPriority::EPriorityRealTimeServer.

158

SYMBIAN OS THREADS AND PROCESSES

that it is no longer possible for a thread to stop another thread in a separate process by calling Suspend(), Terminate(), Kill() or Panic() on it. The functions are retained in EKA2 because a thread can still call the various termination functions on itself or other threads in the same process. RMessagePtr has Kill(), Terminate() and

Panic() methods that are identical to those in RThread. These allow a server to terminate a client thread, for example to highlight a programming error by causing a panic in a client which has passed invalid request data. Chapters 11 and 12 discuss the client–server framework in more detail.

The manner by which the thread was stopped, and its exit reason, can be determined from the RThread handle of an expired thread by calling ExitType(), ExitReason() and ExitCategory(). The exit type indicates whether Kill(), Terminate() or Panic() was called or, indeed, if the thread is still running. The exit reason is the integer parameter value passed to the Kill() or Terminate() functions, or the panic reason. The category is a descriptor containing the panic category, ”Kill” or ”Terminate”. If the thread is still running, the exit reason is zero and the exit category is a blank string.

It is also possible to receive notification when a thread dies. A call to RThread::Logon() on a valid thread handle, passing in a TRequestStatus reference, submits a request for notification when that thread terminates. The request completes when the thread terminates and receives the value with which the thread ended or KErrCancel if the notification request was cancelled by a call to RThread::LogonCancel().

The following example code, which is suitable both for EKA1 and EKA2, demonstrates how you may use thread termination notification. It shows how some long-running synchronous function (SynchronousTask()) can be encapsulated within an active object class which runs the task in a separate thread, allowing the function to be called asynchronously. This is useful if a caller doesn’t want to be blocked on a slow-to-return synchronous function. For example, a third-party library may provide a synchronous API for preparing a file for printing. The UI code which uses it cannot just ”hang” while waiting for the function to return; it requires the operation to be performed asynchronously. Of course, ideally, the function would be asynchronous, implemented incrementally in a low-priority active object, as described in Chapter 9. However, some tasks cannot easily be split into small units of work, or may be ported from code which was not designed for active objects.

The class defined below supplies an asynchronous wrapper, DoAsyncTask(), over a synchronous function, allowing the caller to submit an asynchronous request and receive notification of its completion through a TRequestStatus object.

STOPPING A RUNNING THREAD

159

_LIT(KThreadName, "ExampleThread"); // Name of the new thread

TInt SynchronousTask(); // Example of a long-running synchronous function

class CAsyncTask : public CActive

{// Active object class to wrap a long synchronous task

public:

CAsyncTask();

static CAsyncTask* NewLC();

// Asynchronous request function

void DoAsyncTask(TRequestStatus& aStatus);

protected: // From base class

virtual void DoCancel(); virtual void RunL();

virtual TInt RunError(TInt anError);

private:

CAsyncTask(); void ConstructL();

// Thread start function

static TInt ThreadEntryPoint(TAny* aParameters);

private:

TRequestStatus* iCaller;

// Caller’s request status

RThread iThread;

// Handle to created thread

};

 

The implementation of the active object class is shown below. DoAsyncTask() is the asynchronous request-issuing function into which the caller passes a TRequestStatus object, typically from another active object, which is stored internally as iCaller by the function. Additionally, DoAsyncTask() creates the thread in which the synchronous function will run, calls Logon() upon the thread to receive notification when it terminates, then sets itself active before resuming the thread.

When the thread terminates, the Logon() request completes (the iStatus of the active object receives the exit reason). The active scheduler calls RunL() on the active object which, in turn, notifies the caller that the function call has completed by calling User::RequestComplete() on iCaller, the stored TRequestStatus object.

The class implements the RunError() and DoCancel() functions of the CActive base class. In DoCancel(), the code checks whether the thread is still running because a case could arise where the long-running function has completed and the thread has ended, but the resulting event notification has not yet been handled by the active scheduler. If the thread is outstanding, DoCancel() calls Kill() on it, closes the thread handle and completes the caller with KErrCancel.

CAsyncTask::CAsyncTask()

: CActive(EPriorityStandard) // Standard priority unless good reason

{// Add to the active scheduler

CActiveScheduler::Add(this);

}

160

SYMBIAN OS THREADS AND PROCESSES

// Two-phase construction code omitted for clarity

CAsyncTask:: CAsyncTask()

{// Cancel any outstanding request before cleanup Cancel(); // Calls DoCancel()

//The following is called by DoCancel() or RunL()

//so is unnecessary here too

//iThread.Close(); // Closes the handle on the thread

}

void CAsyncTask::DoAsyncTask(TRequestStatus& aStatus)

{

if (IsActive())

{

TRequestStatus* status = &aStatus;

User::RequestComplete(status, KErrAlreadyExists);

return;

}

//Save the caller’s TRequestStatus to notify them later iCaller = &aStatus;

//Create a new thread, passing the thread function and stack sizes

//No extra parameters are required in this example, so pass in NULL TInt res = iThread.Create(KThreadName, ThreadEntryPoint,

KDefaultStackSize, NULL, NULL); if (KErrNone!=res)

{// Complete the caller immediately User::RequestComplete(iCaller, res);

}

else

{// Set active; resume new thread to make the synchronous call

//(Change the priority of the thread here if required)

//Set the caller and ourselves to KRequestPending

//so the active scheduler notifies on completion

*iCaller = KRequestPending; iStatus = KRequestPending; SetActive();

iThread.Logon(iStatus); // Request notification when thread dies iThread.Resume(); // Start the thread

}

}

TInt CAsyncTask::ThreadEntryPoint(TAny* /*aParameters*/)

{// Perform a long synchronous task e.g. a lengthy calculation TInt res = SynchronousTask();

// Task is complete so end this thread with returned error code RThread().Kill(res);

return (KErrNone);

// This value is discarded

}

 

void CAsyncTask::DoCancel()

{// Kill the thread and complete with KErrCancel // ONLY if it is still running

TExitType threadExitType = iThread.ExitType(); if (EExitPending==threadExitType)

STOPPING A RUNNING THREAD

161

{// Thread is still running iThread.LogonCancel(); iThread.Kill(KErrCancel); iThread.Close();

// Complete the caller User::RequestComplete(iCaller, KErrCancel);

}

}

void CAsyncTask::RunL()

{// Check in case thread is still running e.g. if Logon() failed TExitType threadExitType = iThread.ExitType();

if (EExitPending==threadExitType) // Thread is still running, kill it iThread.Kill(KErrNone);

// Complete the caller, passing iStatus value to RThread::Kill() User::RequestComplete(iCaller, iStatus.Int());

iThread.Close(); // Close the thread handle, no need to LogonCancel()

}

TInt CAsyncTask::RunError(TInt anError)

{

if (iCaller)

{

User::RequestComplete(iCaller, anError);

}

return (KErrNone);

}

For more sophisticated thread death notification, you can alternatively use the RUndertaker thread-death notifier class, which is described in detail in the SDK. By creating an RUndertaker, you receive notification when any thread is about to die. The RUndertaker passes back a notification for each thread death, including the exit reason and the TThreadId. It creates a thread-relative handle to the dying thread, which effectively keeps it open. This means that you must close the thread handle to release the thread finally into the abyss – hence the name of the class. This notification is useful if you want to track the death of every thread in the system, but if you’re interested in the death of one specific thread, it’s easier to use RThread::Logon(), as described above.

It is possible to receive notification when a thread dies by making a call to RThread::Logon() on a valid thread handle. The manner and reason for thread termination can also be determined from the RThread handle of an expired thread by calling ExitType(),

ExitReason() and ExitCategory().

162

SYMBIAN OS THREADS AND PROCESSES

10.4Inter-Thread Data Transfer

On Symbian OS, you can’t transfer data pointers directly between threads running in separate processes, because process address spaces are protected from each other, as I described in Chapter 8.5

On EKA1 versions of Symbian OS, RThread provides a set of functions that enable inter-thread data transfer regardless of whether the threads are in the same or different processes:

IMPORT_C TInt GetDesLength(const TAny* aPtr) const;

IMPORT_C TInt GetDesMaxLength(const TAny* aPtr) const;

IMPORT_C void ReadL(const TAny* aPtr,TDes8& aDes,TInt anOffset) const; IMPORT_C void ReadL(const TAny* aPtr,TDes16 &aDes,TInt anOffset) const; IMPORT_C void WriteL(const TAny* aPtr,const TDesC8& aDes,TInt anOffset)

const;

IMPORT_C void WriteL(const TAny* aPtr,const TDesC16& aDes,TInt anOffset) const;

On EKA2 these methods have been withdrawn from class RThread, but are implemented by RMessagePtr, which can be used by a server to transfer data to and from a client thread. The functions take slightly different parameters but perform a similar role. I’ll discuss inter-thread data transfer here using the RThread API, which is applicable to EKA1 releases. In EKA2 the RMessagePtr API is more intuitive, using a parameter value rather than TAny* to identify the source or target descriptor in the ”other” thread. You should consult an appropriate v8.0 SDK for complete documentation.

On EKA1, RThread::WriteL() can be used to transfer data from the currently running thread to the thread represented by a valid RThread handle. RThread::WriteL() takes a descriptor (8- or 16-bit) containing the data in the current thread and writes it to the thread on whose handle the function is called. The destination of the data in that other thread is also a descriptor, which should be modifiable (derived from TDes). It is identified as a const TAny pointer into the other thread’s address space. The function leaves with KErrBadDescriptor if the TAny pointer does not appear to point to a valid descriptor. The maximum length of the target descriptor can be determined before writing by a call to RThread::GetDesMaxLength().

5 This is true for EKA2 and previous releases of Symbian OS running on target hardware. However, the Symbian OS Windows emulator for releases prior to EKA2 does not protect the address spaces of Symbian OS processes. The threads share writable memory, which means that each emulated process can be accessed by other processes. Code which uses direct pointer access to transfer data between threads in different processes will appear to work on the Windows emulator, then fail spectacularly when deployed on a real Symbian OS handset.