Skip to content

Make the future returned by page.run_task() awaitable - #7008

Open
ndonkoHenri wants to merge 4 commits into
flet-1.1.0from
fix/run-task-awaitable-6515
Open

ndonkoHenri wants to merge 4 commits into
flet-1.1.0from
fix/run-task-awaitable-6515

Conversation

@ndonkoHenri

@ndonkoHenri ndonkoHenri commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #6515.

page.run_task() returned the concurrent.futures.Future from asyncio.run_coroutine_threadsafe(), which a coroutine can't await, so async code couldn't start a task through Flet and wait for its result (await page.run_task(fetch) raised TypeError). The workarounds are worse: .result() blocks the very loop running the task, and page.loop.create_task() skips what run_task() sets up, such as the current page context.

Changes

  • TaskFuture: a concurrent.futures.Future subclass whose __await__ delegates to asyncio.wrap_future(). await page.run_task(handler) returns the handler's value or raises its exception, from the page's loop or any other. run_task() switches the class of the future asyncio already created, so its link to the task is kept. isinstance(f, Future), result(), cancel() and add_done_callback() behave as before. As with an asyncio.Task, cancelling the awaiting coroutine cancels the task, and asyncio.shield() keeps it running.
  • Awaiters no longer hang on a BaseException (found by a Codex review). run_task()'s done callback re-raised the task's exception so concurrent.futures logs it, but that logger only catches Exception. A BaseException, for example the asyncio.CancelledError asyncio makes from a handler's concurrent.futures.CancelledError, skipped the remaining callbacks, including the one that wakes the awaiter. The callback now re-raises only Exceptions. The outcome reaches awaiters through their own callback either way, so logging of normal errors is unchanged.
  • Docs: the TaskFuture and run_task() docstrings (with an example) explain that awaiting returns the value or raises, and that the future keeps its outcome. The async-apps cookbook shows awaiting two tasks, and notes that an un-awaited task's exception is logged.

Testing

Checked against the same run_coroutine_threadsafe() + class switch as run_task(), on Python 3.10, 3.13 and 3.14 (the BaseException cases on 3.10 to 3.14):

Scenario Result
await returns the value ✅
exception raised to the awaiter ✅
asyncio.gather() of several tasks ✅
cancelling the awaiter cancels the task ✅
asyncio.shield() keeps it running ✅
.result() from another thread ✅
still an instance of concurrent.futures.Future ✅
handler raising concurrent.futures.CancelledError / a custom BaseException raised to the awaiter (hung before the second commit)

Also checked end to end in a web app, and the flet test suite passes.

run_task() returned a concurrent.futures.Future, which a coroutine can't
await. It now returns a TaskFuture, a Future subclass whose __await__
wraps asyncio.wrap_future(), so `await page.run_task(...)` returns the
handler's value on any event loop while the existing Future API keeps
working. As with asyncio tasks, cancelling the awaiting coroutine
cancels the task.
run_task()'s done callback re-raised the task's exception so that
concurrent.futures logs it. That logger only catches Exception: a
BaseException raised there, such as the asyncio.CancelledError asyncio
makes from a handler's concurrent.futures.CancelledError, skipped the
remaining done callbacks, including the one that wakes a coroutine
awaiting the future, so the await never returned.

Re-raise only Exception subclasses. The outcome is stored on the future
and reaches awaiters through their own callback either way.
Say in the TaskFuture and run_task() docstrings that awaiting returns the
value or raises, and that the future keeps its outcome afterwards, with
an example. Explain in _on_completion why it re-raises only Exception,
and in the async-apps cookbook that an un-awaited task's exception is
logged.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've reviewed your changes and they look great!


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

@ndonkoHenri ndonkoHenri linked an issue Oct 27, 2026 that may be closed by this pull request
1 task done
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

Deploying flet-website-v2 with  Cloudflare Pages  Cloudflare Pages

Latest commit: 5997835
Status: ✅  Deploy successful!
Preview URL: https://044986f1.flet-website-v2.pages.dev
Branch Preview URL: https://fix-run-task-awaitable-6515.flet-website-v2.pages.dev

View logs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: Tasks created by Page.run_task can not be awaited

1 participant