Repository navigation
Make the future returned by page.run_task() awaitable - #7008
Open
ndonkoHenri wants to merge 4 commits into
Open
ndonkoHenri wants to merge 4 commits into
ndonkoHenri wants to merge 4 commits into
Conversation
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.
1 task done
Deploying flet-website-v2 with
|
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #6515.
page.run_task()returned theconcurrent.futures.Futurefromasyncio.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)raisedTypeError). The workarounds are worse:.result()blocks the very loop running the task, andpage.loop.create_task()skips whatrun_task()sets up, such as the current page context.Changes
TaskFuture: aconcurrent.futures.Futuresubclass whose__await__delegates toasyncio.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()andadd_done_callback()behave as before. As with anasyncio.Task, cancelling the awaiting coroutine cancels the task, andasyncio.shield()keeps it running.BaseException(found by a Codex review).run_task()'s done callback re-raised the task's exception soconcurrent.futureslogs it, but that logger only catchesException. ABaseException, for example theasyncio.CancelledErrorasyncio makes from a handler'sconcurrent.futures.CancelledError, skipped the remaining callbacks, including the one that wakes the awaiter. The callback now re-raises onlyExceptions. The outcome reaches awaiters through their own callback either way, so logging of normal errors is unchanged.TaskFutureandrun_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 asrun_task(), on Python 3.10, 3.13 and 3.14 (theBaseExceptioncases on 3.10 to 3.14):awaitreturns the valueasyncio.gather()of several tasksasyncio.shield()keeps it running.result()from another threadconcurrent.futures.Futureconcurrent.futures.CancelledError/ a customBaseExceptionAlso checked end to end in a web app, and the flet test suite passes.