Add asyncio.run() and asyncio.run_forever() functions.#465
Conversation
|
If my voice counts, I am definitely +1 on having |
Sorry, something went wrong.
|
+1, definitely! About async def wait_for_interrupt():
loop = asyncio.get_event_loop()
future = loop.create_future()
loop.add_signal_handler(signal.SIGINT, future.set_result, None)
try:
await future
finally:
loop.remove_signal_handler(signal.SIGINT) |
Sorry, something went wrong.
|
+1, LGTM |
Sorry, something went wrong.
This would work to support signals. I want try:
await asyncio.forever()
finally:
# important cleanup code codeTBH I don't want to distract ourselves with
For sure. As I explained in the first message, if Guido is in favour of the idea I'll add tests/docs/etc. |
Sorry, something went wrong.
|
Sure, add this. I don't have time for a review though.
|
Sorry, something went wrong.
I've experimented a little bit, and it turns out that it's not that hard to implement the With the latest commit it's possible to write coroutines like this: async def foo():
print('hi')
try:
await asyncio.forever()
except KeyboardInterrupt:
await asyncio.sleep(1)
print('bye')
asyncio.run(foo())The change modifies |
Sorry, something went wrong.
|
So forever is just sleep infinity? Why do we need that?
…--Guido (mobile)
|
Sorry, something went wrong.
It's similar but not the same. It's designed to replace uses of It allows to safely transform this: loop = asyncio.get_event_loop()
server = loop.run_until_complete(
asyncio.start_server(handle_echo, '127.0.0.1', 8888, loop=loop))
try:
loop.run_forever()
except KeyboardInterrupt:
pass
# Close the server
server.close()
loop.run_until_complete(server.wait_closed())
loop.close()into this: async def main():
server = await asyncio.start_server(handle_echo, '127.0.0.1', 8888)
try:
await asyncio.forever()
except KeyboardInterrupt:
pass
server.close()
await server.wait_closed()
asyncio.run(main())The former example that uses The key difference from 'sleep(inf) Having all program bootstrap logic defined in one coroutine is easier than using
If you don't see any pitfalls with |
Sorry, something went wrong.
|
-1. Something that's only meant to be used at the top level doesn't deserve
to be a coroutine IMO.
And even if we have forever() I think you should be able to get the same
behavior (relative to KeyboardInterrupt) with sleep(100000).
|
Sorry, something went wrong.
|
A few comments about
@1st1 I'm not sure it makes sense to have an asynchronous equivalent to
But if the loop is stopped, how can the coroutine keep running? In the current implementation, it is restored by running the loop a second time. So I would argue that a |
Sorry, something went wrong.
Normally yes, I agree. Although I don't think this argument fully applies to this particular use case. The idea is to add APIs to make it possible to move the application bootstrap logic (the "main" function") into a coroutine. The design of
I see your point. Unfortunately it's not possible to implement this behaviour in What if we rename async def main():
server = await asyncio.start_server(handle_echo, '127.0.0.1', 8888)
try:
await asyncio.interrupted()
finally:
server.close()
await server.wait_closed()
asyncio.run(main())
The function is supposed be used to launch your main program coroutine and we recommend to use asyncio in the main thread. And yes, subprocesses don't properly work when the loop isn't running in the main thread. I don't think that lifting this restriction would help anyone to be honest.
Well, we run
Can't do that.
I'm -1 on all three.
It's up to the documentation -- the idea is that Please study my example code in this comment and in #465 (comment). |
Sorry, something went wrong.
|
+1 for keeping |
Sorry, something went wrong.
|
That's also ambiguous (is it a verb or a noun?). And the issue of whether
it would just be a shorthand for sleep(inf) is still unsettled.
|
Sorry, something went wrong.
I think I came up with a solution, please see below.
We can't fix Anyways, I think we can modify With that we can have this: async def main():
server = await asyncio.start_server(handle_echo, '127.0.0.1', 8888)
try:
yield
finally:
server.close()
await server.wait_closed()
asyncio.run(main())I think this is a great idea because:
The |
Sorry, something went wrong.
I think it should rather be |
Sorry, something went wrong.
I like this, disregard my previous comment. |
Sorry, something went wrong.
|
The latest patch is really great. |
Sorry, something went wrong.
|
IIRC people are currently using a bare yield as the equivalent of sleep(0) I really am very concerned that we're going to break things at the very |
Sorry, something went wrong.
|
Also frankly run_in_executor() is a pretty clumsy API due to the initial parameter that is usually None. Is it really important enough to have this? (Again, I worry that this is a panic response rather than something well thought out.) |
Sorry, something went wrong.
But they use it in old style generator-based coroutines: @coroutine
def foo():
yield # this is a NOP yieldasync def main():
try:
yield # <- this is a yield from an async gen, something new in 3.6Asynchronous generators can do any kind of yields they want -- there is no backwards compatibility issue here. In fact, I'm thinking about making
I wouldn't say that it's Nathaniel's post that caused this PR. I've been unhappy about the loop for a long time. I think I first proposed to fix |
Sorry, something went wrong.
Yes, I've been thinking about |
Sorry, something went wrong.
|
Oh, it's async generators. Too subtle. We did fix get_event_loop(), and we're all happy with that. Can we just stop now please? |
Sorry, something went wrong.
I would really prefer option 2: two separate functions -- |
Sorry, something went wrong.
|
I've modified this PR to add just two functions:
This PR also adds unittests. FWIW I used a custom asyncio policy to control the loop that the new functions use during tests, and it worked great. |
Sorry, something went wrong.
|
Thank you Yury, it looks great! I left few small comments concerning error messages. Also I think maybe it is worth adding a note somewhere in docs for people who are already familiar with (This is obvious if one looks at the code, but otherwise it might be not clear why such design was chosen.) |
Sorry, something went wrong.
Sure, we'll update the docs! |
Sorry, something went wrong.
|
We have very long discussion here. |
Sorry, something went wrong.
|
3.6b4 is scheduled for tomorrow, if there is a chance this goes into 3.6, then it probably makes sense to merge this before that time. |
Sorry, something went wrong.
IIUC, Yury is working on a PEP now that will cover the features in this PR. |
Sorry, something went wrong.
|
@ilevkivskyi on your example above on the exception I would actually name the special function differently. instead of : def serve(mgr):
# Set-up loop
loop.run_until_complete(mgr.__aenter__())
try:
loop.run_forever()
except:
result = loop.run_until_complete(mgr.__aexit__(*sys.exc_info()))
else:
result = loop.run_until_complete(mgr.__aexit__(None, None, None))
# Careful clean-upI would do: def serve(mgr):
# Set-up loop
loop.run_until_complete(mgr.__aenter__())
try:
loop.run_forever()
except:
result = loop.run_until_complete(mgr.__on_error__(*sys.exc_info()))
else:
result = loop.run_until_complete(mgr.__aexit__(None, None, None))
# Careful clean-upSo that way if they do not have or define an Or another way is to have some sort of decorator that would register an function with asyncio that would handle the exception message so that way asyncio would not error again if they do not have |
Sorry, something went wrong.
This PR adds two new APIs:
asyncio.run()andasyncio.run_in_executor(). Ideally, if possible, I'd like to have them in 3.6. If I have a green light on the idea, I'll update the patch to add unittests.One of the main complaints that users have about asyncio is the situation around the event loop. We currently do a poor job explaining how exactly it should be used and how to structure asyncio programs in general.
With the recent update of
asyncio.get_event_loop()we can now explain people that passing event loop explicitly is unnecessary, and that library APIs should be designed around coroutines.I think we need to add two more functions to make the loop disappear from most asyncio programs.
asyncio.run_in_executor()coroutine: maps directly to the equivalentloop.run_in_executor(). The idea is that people don't need an event loop to use the function:asyncio.run()function: run a coroutine taking care of the asyncio event loop.Pros:
Simplification of the documentation: I'm working on an update, and one of the things that bothers me that to each and every example have to have a piece of code that manages the loop. For example:
The problem is that the above snippet isn't fully correct, we should have a
try-finallyblock to ensure thatloop.close()is always called. Even in the docs we don't do that. Withasyncio.run()the snippet becomes much shorter:It's currently hard to experiment with asyncio in the REPL, because
get_event_loopandrun_until_completeare rather long names to type. Withasyncio.run():And
asyncio.run()can be called multiple times.Asynchronous generators are properly cleaned-up.
loop.shutdown_asyncgens()is a somewhat low-level advanced API, and I expect something that a lot of people will forget to use.The function promotes a coroutine-centric design. In many cases, is is possible to bootstrap an asyncio program with just one coroutine.
Cons:
It's not possible to completely substitute
loop.run_forever(). One of the documented patterns in asyncio is to userun_foreverto bootstrap servers:To support cases like this, we'll need to add another API. One of the ideas that I have (not for 3.6!) is to add
asyncio.forever()awaitable, so that the above example could be translated to:Adding
asyncio.forever()would require us to add new APIs to event loop, and it is something that clearly requires a thorough review process (I'm thinking about writing a PEP).However, we can probably add
asyncio.run()function in 3.6 to cover some use cases, and enhance it further in 3.7.@gvanrossum, what do you think?