Textualize/textual · error · WorkerError

Request to run a non-async function as an async worker

Error message

Request to run a non-async function as an async worker

What it means

WorkerError from Worker._run_async: the worker was started in async mode but the work item is a plain (non-async) callable. Async workers execute coroutines/awaitables only; sync callables must run as thread workers.

Source

Thrown at src/textual/worker.py:343

        assert loop is not None
        return await loop.run_in_executor(None, runner, self._work)

    async def _run_async(self) -> ResultType:
        """Run an async worker.

        Returns:
            Return value of the work.
        """
        if (
            inspect.iscoroutinefunction(self._work)
            or hasattr(self._work, "func")
            and inspect.iscoroutinefunction(self._work.func)
        ):
            return await self._work()
        elif inspect.isawaitable(self._work):
            return await self._work
        elif callable(self._work):
            raise WorkerError("Request to run a non-async function as an async worker")
        raise WorkerError("Unsupported attempt to run an async worker")

    async def run(self) -> ResultType:
        """Run the work.

        Implement this method in a subclass, or pass a callable to the constructor.

        Returns:
            Return value of the work.
        """
        return await (
            self._run_threaded() if self._thread_worker else self._run_async()
        )

    async def _run(self, app: App) -> None:
        """Run the worker.

        Args:

View on GitHub (pinned to 06dbeef4bb)

Solutions

  1. Add thread=True: `self.run_worker(my_sync_fn, thread=True)` or `@work(thread=True)`.
  2. Or make the function async (`async def`) so the async runner can await it.

Example fix

# before
def load(): ...
self.run_worker(load)
# after
def load(): ...
self.run_worker(load, thread=True)
Defensive patterns

Strategy: validation

Validate before calling

import inspect
if callable(fn) and not inspect.iscoroutinefunction(fn):
    self.run_worker(fn, thread=True)
else:
    self.run_worker(fn)

Type guard

import inspect
def is_async_callable(fn) -> bool:
    f = getattr(fn, 'func', fn)
    return inspect.iscoroutinefunction(f)

Try / catch

from textual.worker import WorkerError
try:
    await worker.run()
except WorkerError:
    worker._run_threaded()

Prevention

When it happens

Trigger: `run_worker(plain_function)` with default exclusive/thread settings that route to _run_async (i.e. not thread=True), or calling `worker.run()` on a Worker wrapping a normal function in async mode.

Common situations: Forgetting the `thread=True` (or @work(thread=True)) argument when passing a blocking function; refactoring an async worker into a sync function without updating flags.

Related errors


AI-assisted analysis of Textualize/textual@06dbeef4bb (2026-08-27). Data as JSON: /api/errors/5a4894b3b5453fcc. Report an issue: GitHub.