Skip to content

expand the run_coroutine_threadsafe recipies #127576

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged

Conversation

graingert
Copy link
Contributor

@graingert graingert commented Dec 3, 2024

I see a lot of people having trouble using run_coroutine_threadsafe correctly:

  • they struggle running a loop in a thread then accessing that loop from the parent thread
  • stopping the loop from another thread
  • stashing the event loop somewhere when spawning a new thread
  • calling back into the event loop from another thread

I've expanded the examples to cover these cases.


📚 Documentation preview 📚: https://cpython-previews--127576.org.readthedocs.build/

@bedevere-app bedevere-app bot added docs Documentation in the Doc dir skip news labels Dec 3, 2024
@graingert graingert marked this pull request as ready for review December 3, 2024 18:57
It's also possible to run the other way around. Example::

@contextlib.contextmanager
def loop_in_thread() -> Generator[asyncio.AbstractEventLoop]:
Copy link
Contributor

Choose a reason for hiding this comment

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

FWIW I think the above simpler example is sufficient, this one looks very contrived to me involving thread pool executor and all.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's used to get any failures out, if they happen when constructing the loop

Copy link
Contributor

@kumaraditya303 kumaraditya303 Dec 29, 2024

Choose a reason for hiding this comment

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

I meant that can we avoid mixing concurrent futures executors with loop? Starting loop in a thread should be sufficient here no? I don't want to encourage such code which creates loop in another thread but runs code from another thread especially using concurrent futures.

Copy link
Contributor Author

@graingert graingert Dec 29, 2024

Choose a reason for hiding this comment

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

You need to be able to start and stop the thread, and collect errors that get raised from asyncio.run, it's complicated to do that with threading.Thread

Copy link
Contributor

Choose a reason for hiding this comment

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

I am thinking of something like this:

from threading import Thread
import contextlib
import asyncio
import concurrent.futures


@contextlib.contextmanager
def get_loop():
    loop_fut = concurrent.futures.Future()
    stop = asyncio.Event()

    async def runner():
        loop_fut.set_result(asyncio.get_event_loop())
        await stop.wait()

    try:
        t = Thread(target=lambda: asyncio.run(runner()))
        t.start()
        loop = loop_fut.result()
        yield loop
    finally:
        loop.call_soon_threadsafe(stop.set)
        t.join()


with get_loop() as loop:
    r = asyncio.run_coroutine_threadsafe(
        asyncio.sleep(1, result="hello"), loop
    ).result()
    print(r)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

But ThreadPoolExecutor(1) does all that for you, and collects any result from asyncio.run. Eg your code will hang if the OS has run out of file descriptors, mine will propagate the exception

Copy link
Contributor

Choose a reason for hiding this comment

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

Okay I take it, I am not really a fan of this pattern but I am fine if you wish.

@kumaraditya303 kumaraditya303 merged commit c9159b7 into python:main Dec 29, 2024
29 checks passed
@graingert graingert deleted the expand-run-coroutine-threadsafe-example branch January 2, 2025 06:30
srinivasreddy pushed a commit to srinivasreddy/cpython that referenced this pull request Jan 8, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
docs Documentation in the Doc dir skip issue skip news
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

2 participants