Java Coroutines

Build Status    Download    Gitter chat

This project contains a pure Java implementation of coroutines. I has a single dependency to the ObjectRelations project.

It can be build locally after cloning by starting a gradle build with gradlew build.

Usage

To include coroutines into a project, add the dependency to your project.

Gradle

dependencies {
    compile 'de.esoco:coroutines:0.9.1'
}

Maven

When using Maven it is necessary to define the JCenter repository. Furthermore, because of an incompatibility with Gradle wildcard versions it is currently necessary to explicitly declare the transitive dependencies of the project in Maven. This will be fixed in a future release. The pom.xml currently needs to contain the following entries:

<repositories>
    <repository>
        <id>jcenter</id>
        <url>https://jcenter.bintray.com/</url>
    </repository>
</repositories>

<properties>
    <esoco.common.version>1.2.0</esoco.common.version>
    <esoco.objectrelations.version>1.3.0</esoco.objectrelations.version>
    <esoco.coroutines.version>0.9.1</esoco.coroutines.version>
</properties>

<dependencies>
    <dependency>
        <groupId>de.esoco</groupId>
        <artifactId>coroutines</artifactId>
        <version>${esoco.coroutines.version}</version>
    </dependency>
    <dependency>
        <groupId>org.obrel</groupId>
        <artifactId>objectrelations</artifactId>
        <version>${esoco.objectrelations.version}</version>
    </dependency>
    <dependency>
        <groupId>de.esoco</groupId>
        <artifactId>esoco-common</artifactId>
        <version>${esoco.common.version}</version>
    </dependency>
</dependencies>

The following gives only a short overview of how to use this project. More detailed information can be found on our documentation site and in the generated javadoc.

Declaring Coroutines

Coroutines are used in two stages: the first is the declaration of coroutines, the second their execution. To declare coroutines this framework provides a simple builder pattern that starts with the invocation of the static factory method Coroutine.first(CoroutineStep). This creates a new coroutine instance that invokes a certain execution step. Afterward the coroutine can be extended with additional steps by invoking the instance method Coroutine.then(CoroutineStep). Each call of then() will return a new coroutine instance because coroutines are immutable. That allows to use existing coroutines as templates for derived coroutines.

Coroutine Steps

The execution steps of a coroutine are instances of the abstract class CoroutineStep. The sub-package step contains several pre-defined step implementations as well as several asynchronous I/O steps in the sub-packagestep.nio. All framework steps have static factory methods that can be used in conjunction with the coroutine builder methods and by applying static imports to declare coroutines with a fluent API. A simple example based on the step CodeExecution which executes functional expressions would look like this:

import static de.esoco.coroutine.Coroutine.*;
import static de.esoco.coroutine.step.CodeExecution.*;

Coroutine<String, Integer> parseInteger =
    Coroutine.first(apply((String s) -> s.trim()))
             .then(apply(s -> Integer.valueOf(s)));

Executing Coroutines

The execution of coroutines follows the pattern of structured concurrency and therefore requires an enclosing CoroutineScope. Like coroutines the scope provides a static factory method that receives a functional interface implementation which contains the code to run. That code can then run arbitrary coroutines asynchronously in the scope like in this example:

Coroutine<?, ?> crunchNumbers =
    first(run(() -> Range.from(1).to(10).forEach(Math::sqrt)));

CoroutineScope.launch(scope -> {
    // start a million coroutines
    for (int i = 0; i < 1_000_000; i++) {
        crunchNumbers.runAsync(scope);
    }
});

Any code that would follow after the scope launch block will only run after all coroutines have finished execution, either regularly or with an error. In the latter case the scope would also throw an exception if at least one coroutine fails.

Continuation

Each start of a coroutine produces a Continuation object that can be used to observe and if necessary cancel the asynchronous coroutine execution. If the coroutines has finished execution it provides access to the produced result (if such exists).

ScopeFuture

If a coroutine scope needs to produce a value it can be started by means of the produce method instead of launch. That method returns immediately with an instance of java.util.concurrent.Future which can then be used to monitor the scope execution and to query the result or error state after completion. This method can also be used if handling a scope through a future is preferred over a launch block. In any case the execution of the asynchronous code is contained and can be supervised by the application. A simple example:

Future<String> futureResult = 
  produce(scope -> getScopeResult(scope), 
          scope -> someCoroutine.runAsync(scope, "input"));

String result = futureResult.get();

Project Structure

The main package of this project is de.esoco.coroutine. It contains the core classes of the framework:

Furthermore the main package contains several exception classes that are used by the framework. All exceptions are unchecked and should therefore always be considered in an application's exception handling.

The sub-package step contains several standard coroutine step implementations. All step classes provides factory methods that can be used for a fluent declaration of coroutines based on the methods

The folder src/examples contains some examples for using coroutines.

License

This project is licensed under the Apache 2.0 license (see LICENSE file for details).