Active Object is a well known design pattern for synchronizing access to an object and/or resource. The basic idea is to separate method invocation from method execution which is done in a dedicated thread.
Instead of using the objects interface directly, a client of an Active Object uses some kind of proxy which enqueues a so-called Method Request for later execution. The proxy finishes immediately and returns to the client some sort of callback, or variable, by which the client can receive the result. These intermeditate result variables are also known as Futures.
As always, there are lots of ways to implement this pattern. For example, if you had an interface like this
class MyObject { public: int doStuff(const std::string& param) =0; std::string doSomeOtherThing(int i) =0; };
applying a straight forward implementation, you would first transform this into a proxy and method request classes:
class MyObjectProxy { public: MyObjectProxy(MyObject* theObject); // proxy methods Future<int> doStuff(const std::string& param); Future<std::string> doSomeOtherThing(int i); private: MyObject * _myObject; }; class MethodRequest_DoStuff : public AbstractMethodRequest { public: MethodRequest_DoStuff(const std::string& param); // all method request classes must implement execute() virtual void execute(MyObject* theObject); private: const std::string _param; };
… and so on (for more details see this basic paper by Douglas C. Schmidt, or read chapter Concurrency Patterns in POSA2).
It’s easy to see that this implementation produces a lot of boilerplate code. To solve this, you could either cook up some code generation, or look for some language support to reduce the amount of characters you have to type. In C++, some sort of template solution can be the way to go, or…
Introducing Active Methods
With class ActiveMethod together with support classes ActiveDispatcher and ActiveResult the POCO C++ libraries provide very simple and elegant building blocks for implementing the Active Object pattern.
ActiveMethod: this is the core piece. When called, an ActiveMethod executes in its own thread.
ActiveResult: this is what I referred to earlier as a Future. Instances of ActiveResult are used to pass the result of an ActiveMethod call back to the client.
ActiveDispatcher: if you only use ActiveMethods, every ActiveMethod thread can execute in parallel. With ActiveDispatcher as base class, ActiveMethod calls are serialized, thus implementing real™ Active Object behaviour.
Here my earlier example using ActiveMethods:
class MyObject { public: // ActiveMethods are initialized in the ctors // initializer list MyObject() : doStuff(this, &MyObject::doStuffImpl), doSomeOtherThing(this, &MyObject::doSomeOtherThingImpl) {} ActiveMethod<int, std::string, MyObject> doStuff; ActiveMethod<std::string, int, MyObject> doSomeOtherThing; private: int doStuffImpl(const std::string& param); std::string doSomeOtherThingImpl(int i); };
This is used as follows:
MyObject myObject; ActiveResult<std::string> result = myObject.doSomeOtherThing(42); ... result.wait(); std::cout << result.data() << std::endl;
This solution requires minimal amounts of additional code to transform your lame and boring normal object into a full-fledged Active Object. The only downside is that Active Methods currently can only have one parameter. If you need more, you have to use tuples or parameter objects.
Have fun!
Hi, awesome tutorial man!
How about returning references instead of copies?
I’ve tried to change the return type but I`m not sure how to use the ActiveResult to do it.