This rule raises when a control flow statement (return, break, continue) is used inside a TaskGroup or Nursery context manager.

Why is this an issue?

Using control flow statements like return, break, or continue inside async TaskGroup or Nursery blocks leads to counterintuitive behavior that can confuse developers and introduce bugs.

What is the potential impact?

Deferred execution in TaskGroup

In asyncio’s TaskGroup, control flow statements don’t take immediate effect. Instead, they wait for all tasks in the group to complete before executing. This can lead to:

Lost return values in Nurseries

In Trio and AnyIO nurseries, return values can be lost if other tasks in the nursery raise exceptions:

How to fix it in Asyncio

Move the control flow statement outside the TaskGroup or Nursery block, and use the appropriate cancellation mechanism before exiting the block.

Code examples

Noncompliant code example

import asyncio

async def process():
    async with asyncio.TaskGroup() as tg:
        tg.create_task(background_task())

        if condition():
            return "result"  # Noncompliant: waits for background_task() to complete

Compliant solution

import asyncio

async def process():
    result = None
    async with asyncio.TaskGroup() as tg:
        task = tg.create_task(background_task())

        if condition():
            result = "result"
            task.cancel()

    return result

How to fix it in Trio

Move the control flow statement outside the Nursery block, and use the appropriate cancellation mechanism before exiting the block.

Code examples

Noncompliant code example

import trio

async def process():
    async with trio.open_nursery() as nursery:
        nursery.start_soon(background_task)

        if condition():
            return "result"  # Noncompliant: value may be lost

Compliant solution

import trio

async def process():
    result = None
    async with trio.open_nursery() as nursery:
        nursery.start_soon(background_task)

        if condition():
            result = "result"
            nursery.cancel_scope.cancel()

    return result

How to fix it in AnyIO

Move the control flow statement outside the TaskGroup block, and use the appropriate cancellation mechanism before exiting the block.

Code examples

Noncompliant code example

import anyio

async def process():
    async with anyio.create_task_group() as tg:
        tg.start_soon(background_task)

        if condition():
            return "result"  # Noncompliant: waits for background_task

Compliant solution

import anyio

async def process():
    result = None
    async with anyio.create_task_group() as tg:
        tg.start_soon(background_task)

        if condition():
            result = "result"
            tg.cancel_scope.cancel()

    return result

Resources

Documentation