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
.
To include coroutines into a project, add the dependency to your project.
dependencies {
compile 'de.esoco:coroutines:0.9.1'
}
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.
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.
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)));
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.
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).
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();
The main package of this project is de.esoco.coroutine
. It contains the core classes of the framework:
first(CoroutinesStep)
to create new declarations and the instance method then(CoroutineStep)
to extend an existing coroutine. Coroutine instances are always immutable and extending them will always create a new instance.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
apply(Function)
, consume(Consumer)
, supply(Supplier)
, run(Runnable)
.Iterable
input value.ChannelSend and ChannelReceive: These steps suspend the execution of a coroutine until sending to or receiving from a channel succeeded.
Finally, the sub-package step.nio
contains several step implementations that use the asynchronous APIs of the java.nio
package to implement suspending I/O functionality.
The folder src/examples contains some examples for using coroutines.
This project is licensed under the Apache 2.0 license (see LICENSE file for details).