What does WorkQueue.await() do, when is it actually needed, and what are the downsides of calling it inside a task action?
answer
- blocks until submitted work done
- rethrows WorkerExecutionException
- Gradle awaits implicitly at task end
- needed only for intra-action phase ordering
- blocks a build thread -> reduces parallelism
basics
~20 sawait() blocks the task action until all work submitted to that queue finishes, and rethrows any worker failures as a WorkerExecutionException. You rarely need it because Gradle awaits automatically at task end; use it only when later steps in the same action depend on the work's output.
solid answer
~50 s`WorkQueue.await()` (and `WorkerExecutor.await()`) blocks the current task action thread until all previously submitted work in that queue has completed, surfacing any failures as a `WorkerExecutionException`. **It is usually unnecessary**: Gradle automatically waits for all submitted work before the task is considered finished, so if your action just submits units and returns, you don't call await. You need it only when the **same task action** must consume the workers' results before doing more work — e.g. submit phase 1, `await()`, then read the produced files and submit phase 2, or aggregate results. The downside is that `await()` makes the task action **block a build thread**, which can reduce overall parallelism (that thread is occupied waiting instead of doing other useful work) and can interact poorly with worker budget. So treat await as a dependency-ordering tool within one action, not a routine call; if you can express the dependency as task ordering or output files instead, prefer that.
code
kotlin · 7 lines@TaskAction
fun run() {
val queue = workerExecutor.noIsolation()
inputs.get().forEach { queue.submit(GenerateAction::class.java) { it.input.set(it) } }
queue.await() // barrier: ensure generation finished before consuming
consumeGeneratedFiles().forEach { queue.submit(PackageAction::class.java) { it.file.set(it) } }
}go deeper
Know await() waits for submitted work and that Gradle usually waits for you automatically.
Explain the intra-action two-phase scenario where await is genuinely needed and that it rethrows worker failures.
Discuss the thread-blocking/parallelism cost and prefer task-level wiring over in-action barriers when possible.
Advise on patterns that avoid barriers entirely (independent submissions, task graph wiring) to maximize build concurrency across a plugin suite.
## What await() does `WorkQueue.await()` blocks the calling (task action) thread until **every unit previously submitted to that queue** has finished. If any unit threw, `await()` rethrows the aggregated failure as a `WorkerExecutionException`. `WorkerExecutor.await()` does the same across all queues created from that executor in the current action. ```kotlin @TaskAction fun run() { val queue = workerExecutor.noIsolation() inputs.forEach { queue.submit(Phase1::class.java) { it.input.set(it) } } queue.await() // wait for phase 1 to finish val intermediate = readIntermediate() intermediate.forEach { queue.submit(Phase2::class.java) { it.data.set(it) } } // Gradle awaits phase 2 automatically at task end } ``` ## When you actually need it Gradle **already awaits all submitted work before the task completes**. So in the common case — submit N independent units, return — you never call `await()`. You need it only when, **within a single task action**, a later step depends on the output of earlier submitted work: - Two-phase pipelines (generate, then consume the generated artifacts). - Aggregating results from workers before computing a summary. - Failing fast in the middle of the action if a unit failed. ## Downsides 1. **Blocks a build thread.** While the action sits in `await()`, that thread is parked, not advancing other work. With limited workers this can lower throughput or, in pathological cases, contribute to under-utilization. 2. **Couples the action to timing.** It turns an otherwise fire-and-forget submission into a synchronous barrier, making the task action longer-lived and harder to reason about. 3. **Error timing changes.** Failures surface at the `await()` point as `WorkerExecutionException` rather than at task end — you must handle/propagate them appropriately. ## Better alternatives when possible - If two batches of work are independent, don't await between them — submit both and let Gradle await once at the end. - If a dependency is really between tasks, model it as separate tasks with input/output wiring so Gradle's scheduler handles ordering and parallelism, instead of an in-action barrier. ## Key takeaway `await()` is a **scoped barrier** for intra-action ordering, not a 'make work run' switch. Use sparingly; default to letting Gradle's implicit end-of-task await handle completion.
- If you never call await(), can a worker failure go unnoticed?No. Gradle awaits all submitted work at task end and propagates any WorkerExecutionException there, failing the task. await() only changes when the failure surfaces, not whether it does.
- Why might calling await() hurt build performance?It parks the task action thread until the queue drains. That thread can't do other useful work while waiting, so with a limited worker budget it can reduce overall concurrency and throughput.
saying these in an interview costs you the question
- Saying you must call await() or the work won't execute.
- Calling await() after every submit by default, serializing what could run in parallel.
- Ignoring that await() rethrows worker exceptions, so failures must be handled at that point.