Coroutine
. A new coroutine is created either by invoking the constructor or by calling the static factory method Coroutine.first()
. The latter is recommended because it is part of the fluent interface for the declaration of coroutines. It's complement is the instance method Coroutine.then()
which extends a coroutine with additional functionality.CoroutineStep
as their argument. The framework already contains several step implementations that cover the most common uses. The most generic step steps is CodeExecution
. As all framework steps it provides static factory methods that supplement the fluent coroutine API. By invoking these methods with lambda expressions or method references a coroutine can be extended with arbitrary code. Here’s a simple example of such a declaration:Function
interface. Hence coroutines can be invoked with a specific input value to process and may return the result of a computation.CodeExecution
contains factory methods for the standard functional interfaces of Java. These method are generically typed so that Coroutine.then()
will only accept code with an input type that matches the current output of the coroutine and creates a new coroutine with the same output type as the code. This allows to chain arbitrary steps into larger coroutines with a defined in- and output.CoroutineScope
. The main property of a scope is that it will block the invoking thread unless all coroutines that have been started in it have terminated, either successfully, by cancellation, or with an error. And if an error occurred in any coroutine the scope will also throw an exception instead of just continuing the current thread. This prevents losing track of coroutine executions and their error conditions.});
) it would not run before all coroutines have finished because as explained, the scope will await the termination of all it’s children.TEXT
which is actually a relation type from the ObjectRelations framework. The usage of relations in coroutines is explained at the end of this article.Continuation
is returned as shown in the example code below. The continuation has the same generic type as the output type of the associated coroutine and serves two purposes:CodeExecution
class mentioned before has alternative factory methods that accept binary functions with an additional continuation argument. The step can then use the continuation to get or set state values, query configuration data, or access the scope it is running in.ForkJoinPool.commonPool()
). But the thread pool and other parameters of the execution are actually provided by the CoroutineContext
. The purpose of the context is to provide coroutines in one or more scopes with an execution environment. It can also be used to share configuration and synchronized resources between scopes.Coroutines
class. But it is also possible to instantiate and configure contexts only for certain executions if necessary. A non-default context can be provided when launching a new scope which will then use the context for all coroutines it launches. But for many use cases the default context should be sufficient.java.nio.Channel
Interface). Channels are queue-like data structures with a fixed capacity that data can be written to or read from. A coroutine that tries to read from an empty channel or write to a full channel will be suspended until data or capacity becomes availability. ChannelId
which has the main purpose of defining the channel data type with it’s generic type parameter. The following code shows an example of channel communication. It again uses static imports for all key elements for a concise notation.ChannelSend
and ChannelReceive
(through their static factory methods) with the channel ID as their configuration parameter.Threads.sleep(1000)
in this example only serves to demonstrate the suspension of the receiving coroutine (Threads is a class from the dependency that also performs exception handling). When the receive coroutine is started the channel with the ID testChannel will be created automatically with a capacity of 1 and is initially empty. Therefore the coroutine is suspended until data becomes available in the channel.CoroutineStep
to create new steps if the existing implementations don’t suffice.CallSubroutine
. This allows to compose new coroutines from existing ones and therefore to build libraries of coroutine functionalities as a base for such compositions. The static factory method is call.Condition
which provides multiple variants to execute different steps, depending on the evaluation of a predicate:java.lang.Iterable
interface the Iteration
step can be used. It applies another coroutine step to each element of a collection and can optionally collect the results into a new collection. As with loops the coroutine is suspended between each iteration round. The following example revisits the first coroutine execution example and uses the iteration step to process each element in a range:Thread.sleep
. But that method must be avoided when working with thread pools because letting a pooled thread sleep will block it for any other use. Furthermore, with coroutines it is desirable to suspend the execution while a coroutine is delayed.Delay
which has several factory methods that suspend a coroutine for a certain time. This can be used as any other step and the implementation behind it should be sufficient for most use cases. But it’s inner workings are a bit different than that of other steps and may need to be considered under special circumstances, e.g. if a lot of delayed executions are performed.ScheduledExecutorService
which is queried from the context through the getScheduler method. By default it is initialized with a scheduled thread pool with a size of 1. So, if a lot of scheduling should take place it may be adequate to configure a different scheduler. Furthermore, the scheduler may compete with the standard thread pool which typically uses all available system threads. In such case alternative thread pool configurations can be set in the coroutine context, either in the default context or a dedicated one for the affected execution(s).Coroutines
class which contains global definitions. Among those are several event listener relation types. These listeners can then be set on either coroutines, continuations, scopes, or contexts and will be notified by the framework when the associated events happen. Such listeners can be very helpful for debugging or the management of coroutine executions.MANAGED
set on it will be automatically closed when the coroutine or scope finishes in any state. This behavior is similar to Java’s try-with-resources blocks and is used by the NIO steps mentioned above to automatically close the socket or file channels they operate on.