Skip to content
Paulo Lieuthier edited this page Apr 14, 2016 · 3 revisions

How it works

Each task is an instance of class org.df4j.core.Actor. This class contains inner classes which represent task parameters. An Actor without parameters can be declared, but has little advantage over plain Runnable.

Most important parameter class is StreamInput. It contains internal queue to keep multiple argument values, so arguments for next task executions can be submitted asynchronously.

Other parameter classes can be considered as specializations of the StreamInput class.

Class Semafor contains no storage for argument values, only event counter. It can be used, for example, to control how many resources (memory, internet connections etc) the actor is allowed to allocate. When the counter become zero, actor does not start.

Class Input contains storage for only one argument value.

Class ConstInput also contains storage for only one argument. It differs from class Input so that its value can be set only once, and is not consumed after task execution.

User can declare his own parameter class which extends one of the above listed classes.

To define a user-specific Actor, programmer needs to instantiate its parameters and declare method act() which will be invoked after all parameters are assigned. Inside this method, argument values can be obtained from parameter instances with method parameter.get().

After the method act() returns, current argument values are consumed, and next values (if any) are retrieved from parameters of type StreamInput. Values of type Input are just cleared. Counters in Semafores are decreased by 1. If after that all parameters remain active, the actor task is resubmitted to the executor. Note that resubmission is made only after the metod act() returns, even if all arguments for next excution were ready before the method act() exited. So the method act() can be considered as synchronized.

Programmer also can declare other instance fields, like in any other class. They do not affect actor's execution.

Hello world example

**
* collects strings
* prints collected strings when argument is an empty string 
*/
class Collector extends Actor {
    StreamInput<String> input = new StreamInput<String>(); // actor's parameter
    StringBuilder sb = new StringBuilder(); 
    
    @Override
    protected void act() {
        String message = input.get();
        // empty string indicates the end of stream
        // nulls are not allowed
        if (message == "") {
            System.out.println(sb.toString());
            sb.setLength(0);
        } else {
           sb.append(message);
           sb.append(" ");
        }
    }
}

public void test() {
    Collector coll = new Collector();
    coll.input.post("Hello");
    coll.input.post("World");
    coll.input.post("");
}

In cases like this, where Actor has single or dominant StreamInput parameter, utility class Actor1 with predefined StreamInput parameter can be used:

class Collector extends Actor1<String> {
    StringBuilder sb = new StringBuilder();
    
    @Override
    protected void act(String message) {
        if (message == "") {
            System.out.println(sb.toString());
            sb.setLength(0);
        } else {
           sb.append(message);
           sb.append(" ");
        }
    }
}

public void test() {
    Collector coll = new Collector();
    coll.post("Hello");
    coll.post("World");
    coll.post("");
}

Here argument from the main parameter is passed to the metod act directly, and messages are sent to the actor itself. This simplifies both access to frontend pins and declaration of backend procedure, but adds nothing essential - just a sintactic sugar.

Next

Theoretical background: https://github.com/rfqu/df4j/wiki/Theory

Clone this wiki locally