5.4 Active Objects - Reference Documentation
Authors: The Whole GPars Gang
Version: 1.0-SNAPSHOT
5.4 Active Objects
Active objects provide an OO facade on top of actors, allowing you to avoid dealing directly with the actor machinery, having to match messages, wait for results and send replies.Actors with a friendly facade
import groovyx.gpars.activeobject.ActiveObject import groovyx.gpars.activeobject.ActiveMethod@ActiveObject class Decryptor { @ActiveMethod def decrypt(String encryptedText) { return encryptedText.reverse() } @ActiveMethod def decrypt(Integer encryptedNumber) { return -1*encryptedNumber + 142 } }final Decryptor decryptor = new Decryptor() def part1 = decryptor.decrypt(' noitcA ni yvoorG') def part2 = decryptor.decrypt(140) def part3 = decryptor.decrypt('noitide dn')print part1.get() print part2.get() println part3.get()
By default, all active methods are set to be non-blocking . However, methods, which declare their return type explicitly, must be configured as blocking, otherwise the compiler will report an error. Only def , void and DataflowVariable are allowed return types for non-blocking methods.Under the covers, GPars will translate your method call to a message being sent to the internal actor . The actor will eventually handle that message by invoking the desired method on behalf of the caller and once finished a reply will be sent back to the caller. Non-blocking methods return promises for results, aka DataflowVariables .
But blocking means we're not really asynchronous, are we?
Indeed, if you mark your active methods as blocking , the caller will be blocked waiting for the result, just like when doing normal plain method invocation. All we've achieved is being thread-safe inside the Active object from concurrent access. Something the synchronized keyword could give you as well. So it is the non-blocking methods that should drive your decision towards using active objects. Blocking methods will then provide the usual synchronous semantics yet give the consistency guarantees across concurrent method invocations. The blocking methods are then still very useful when used in combination with non-blocking ones.import groovyx.gpars.activeobject.ActiveMethod import groovyx.gpars.activeobject.ActiveObject import groovyx.gpars.dataflow.DataflowVariable@ActiveObject class Decryptor { @ActiveMethod(blocking=true) String decrypt(String encryptedText) { encryptedText.reverse() } @ActiveMethod(blocking=true) Integer decrypt(Integer encryptedNumber) { -1*encryptedNumber + 142 } }final Decryptor decryptor = new Decryptor() print decryptor.decrypt(' noitcA ni yvoorG') print decryptor.decrypt(140) println decryptor.decrypt('noitide dn')
Non-blocking semantics
Now calling the non-blocking active method will return as soon as the actor has been sent a message. The caller is now allowed to do whatever he likes, while the actor is taking care of the calculation. The state of the calculation can be polled using the bound property on the promise. Calling the get() method on the returned promise will block the caller until a value is available. The call to get() will eventually return a value or throw an exception, depending on the outcome of the actual calculation.The get() method has also a variant with a timeout parameter, if you want to avoid the risk of waiting indefinitely.
Annotation rules
There are a few rules to follow when annotating your objects:- The ActiveMethod annotations are only accepted in classes annotated as ActiveObject
- Only instance (non-static) methods can be annotated as ActiveMethod
- You can override active methods with non-active ones and vice versa
- Subclasses of active objects can declare additional active methods, provided they are themselves annotated as ActiveObject
- Combining concurrent use of active and non-active methods may result in race conditions. Ideally design your active objects as completely encapsulated classes with all non-private methods marked as active
Inheritance
The @ActiveObject annotation can appear on any class in an inheritance hierarchy. The actor field will only be created in top-most annotated class in the hierarchy, the subclasses will reuse the field.import groovyx.gpars.activeobject.ActiveObject import groovyx.gpars.activeobject.ActiveMethod import groovyx.gpars.dataflow.DataflowVariable@ActiveObject class A { @ActiveMethod def fooA(value) { … } }class B extends A { }@ActiveObject class C extends B { @ActiveMethod def fooC(value1, value2) { … } }
Groups
Just like actors can be grouped around thread pools, active objects can be configured to use threads from particular parallel groups.@ActiveObject("group1")
class MyActiveObject {
…
}
final DefaultPGroup group = new DefaultPGroup(10) ActiveObjectRegistry.instance.register("group1", group)
Alternative names for the internal actor
You will probably only rarely run into name collisions with the default name for the active object's internal actor field. May you need to change the default name internalActiveObjectActor , use the actorName parameter to the @ActiveObject annotation.@ActiveObject(actorName = "alternativeActorName")
class MyActiveObject {
…
}
Alternative names for internal actors as well as their desired groups cannot be overriden in subclasses. Make sure you only specify these values in the top-most active objects in your inheritance hierarchy. Obviously, the top most active object is still allowed to subclass other classes, just none of the predecessors must be an active object.