1. Introduction
Dataflow programming lets you express dependencies between concurrent computations declaratively. Values are bound once and read many times — the runtime handles ordering automatically. You never need to think about which thread runs first.
Groovy’s dataflow support integrates natively with
async/await and the
Awaitable API.
2. DataflowVariable
A DataflowVariable is a single-assignment variable. It starts unbound,
can be written to exactly once, and any thread that reads it before
binding will block until a value is available.
import groovy.concurrent.DataflowVariable
def x = new DataflowVariable()
def y = new DataflowVariable()
def z = new DataflowVariable()
async {
z << await(x) + await(y) // blocks until x and y are bound
}
async { x << 10 } // bind x — order doesn't matter
async { y << 5 } // bind y
println "Result: ${await(z)}" // 15
Key properties:
-
Write-once: calling
bind()or<<a second time throwsIllegalStateException -
Implements
Awaitable: works withawait, combinators (Awaitable.all), andthenchaining -
Thread-safe: safe to bind from one thread and read from any number of others
2.1. Error binding
def v = new DataflowVariable()
v.bindError(new RuntimeException('computation failed'))
// Any thread that awaits v will receive the exception
2.2. Chaining with then
def v = new DataflowVariable()
def doubled = v.then { it * 2 }
async { v << 21 }
assert await(doubled) == 42
2.3. Combining multiple variables
def a = new DataflowVariable()
def b = new DataflowVariable()
def c = new DataflowVariable()
async { a << 1 }
async { b << 2 }
async { c << 3 }
def results = await(a, b, c)
assert results == [1, 2, 3]
3. Dataflows
Dataflows is a convenience class that auto-creates DataflowVariable
instances on demand. Property reads block until bound; property writes
bind the variable.
import groovy.concurrent.Dataflows
def df = new Dataflows()
async { df.z = df.x + df.y } // blocks on x and y, binds z
async { df.x = 10 }
async { df.y = 5 }
println "Result: ${df.z}" // 15
This is the most concise way to write dataflow programs in Groovy. Variables are created lazily — no declarations needed.
def df = new Dataflows()
async { df.fullName = "${df.first} ${df.last}" }
async { df.first = 'John' }
async { df.last = 'Doe' }
assert df.fullName == 'John Doe'
3.1. Accessing the underlying variable
Use getVariable() when you need the DataflowVariable itself
(e.g., to pass to Awaitable.all or check isBound):
def df = new Dataflows()
def v = df.getVariable('x') // DataflowVariable, does not block
assert !v.isBound()
async { df.x = 42 }
assert await(v) == 42