Rogue Wave banner
Previous fileTop of DocumentContentsIndexNext file

3.8 Using Threads To Build Active Objects

You can encapsulate asynchronous operations within an active object. An active object creates one or more threads to asynchronously execute service requests made by its clients.

This is useful because IOUs (described in Section 5.5) are not always closed and redeemed when threads exit. Although the RWTThreadIOUFunction class closes the IOU just before exiting, it is possible to create threads that can close an IOU at any time during their processing.

3.8.1 Using Runnables

Using an active object solves the join problem by creating any necessary threads within the active object's constructor and joining with them in its destructor. This pattern is similar to the "resource acquisition is initialization" idiom employed by the Synchronization package guard objects (see Section 4.4.3).

Any of the threaded runnable classes, which are part of the Threading package, can construct an active object. The final choice, though, is dependent on the type of service that the object provides.

Runnables are covered in detail in Section 3.5, "The Runnable Object Classes."

3.8.1.1 Example

Example 25 shows how an asynchronous function call can be converted to a simple active object.

Example 25 -- Encapsulating asynchronous operations in an active object

//1Define the actual service that is to be executed in a separate thread.
//2Define a constructor that creates a thread to perform the operation.
//3Define a destructor that joins the thread.
//4Include an accessor for retrieving the IOU result of the operation.
//5Construct a named object to start the operation.
//6Retrieve the future result of the operation as an IOU.
//7Redeem the result, blocking (if necessary) until the operation has completed.
//8Destroy the service object at end of scope, and automatically join with the thread that was created.

3.8.1.2 Other Solutions to the Join Problem

The approach in the previous example solves the join problem, but the implementation is confusing because:

A better way to solve the problem is to move the initiation of the asynchronous operation to a public member function of the class. In this case, the operation is started by calling the member function, which creates a threaded runnable to perform the actual operation. A handle to that runnable could be stored within the class so that a join can be performed when the object is destroyed.

Other problems are created, however, if the service is invoked a second time:

To avoid these new problems:

3.8.2 Using Runnable Servers

The RWTThreadIOUFunction used in the example is generally not suitable for an active object implementation that must accept any number of asynchronous requests from a client interface.

It is better to use one of the runnable server classes, RWRunnableServer or RWServerPool, to provide the internal thread or threads required by the active object. These classes are suitable because they are designed to continuously accept and execute other runnable objects. This allows you to package individual operations as synchronous runnables that can be passed to the internal server for execution. Runnable servers are covered in detail in Section 3.6, "The Server Classes."

In Example 26, individual operations are executed asynchronously relative to the thread that requests them, but simultaneous requests cannot be processed concurrently. This limitation is a consequence of choosing the single-threaded RWRunnableServer class for the internal server thread.

If an active object design can benefit from increased concurrency, then the multi-threaded RWServerPool class should be used instead.

Example 26 -- Using a single-threaded runnable server class

//1Define the actual service to be performed.
//2Define a constructor that creates and starts the internal server.
//3Define a destructor that stops the internal server and waits for it to exit.
//4Define a member function to initiate an asynchronous operation by constructing a synchronous runnable and passing it to the internal server. The IOU representing the result of the operation is returned to the caller.
//5Construct an instance of the service provider.
//6Call the asynchronous service and save the IOU result.
//7Redeem the IOU result, blocking if necessary, until the result is made available.
//8The service provider object is destroyed at block scope, but the destructor does not return until its internal server thread has shutdown and exited.

Previous fileTop of DocumentContentsIndexNext file

©Copyright 2000, Rogue Wave Software, Inc.
Contact Rogue Wave about documentation or support issues.