{"action":"created","issue":{"url":"https://api.github.com/repos/python/asyncio/issues/465","repository_url":"https://api.github.com/repos/python/asyncio","labels_url":"https://api.github.com/repos/python/asyncio/issues/465/labels{/name}","comments_url":"https://api.github.com/repos/python/asyncio/issues/465/comments","events_url":"https://api.github.com/repos/python/asyncio/issues/465/events","html_url":"https://github.com/python/asyncio/pull/465","id":189188609,"number":465,"title":"Add asyncio.run() and asyncio.run_in_executor() functions.","user":{"login":"1st1","id":239003,"avatar_url":"https://avatars.githubusercontent.com/u/239003?v=3","gravatar_id":"","url":"https://api.github.com/users/1st1","html_url":"https://github.com/1st1","followers_url":"https://api.github.com/users/1st1/followers","following_url":"https://api.github.com/users/1st1/following{/other_user}","gists_url":"https://api.github.com/users/1st1/gists{/gist_id}","starred_url":"https://api.github.com/users/1st1/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/1st1/subscriptions","organizations_url":"https://api.github.com/users/1st1/orgs","repos_url":"https://api.github.com/users/1st1/repos","events_url":"https://api.github.com/users/1st1/events{/privacy}","received_events_url":"https://api.github.com/users/1st1/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":11,"created_at":"2016-11-14T18:29:54Z","updated_at":"2016-11-15T19:03:51Z","closed_at":null,"pull_request":{"url":"https://api.github.com/repos/python/asyncio/pulls/465","html_url":"https://github.com/python/asyncio/pull/465","diff_url":"https://github.com/python/asyncio/pull/465.diff","patch_url":"https://github.com/python/asyncio/pull/465.patch"},"body":"This PR adds two new APIs: `asyncio.run()` and `asyncio.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.\r\n\r\nOne 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.\r\n\r\nWith 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.\r\n\r\nI think we need to add two more functions to make the loop disappear from most asyncio programs.\r\n\r\n-----\r\n\r\n`asyncio.run_in_executor()` coroutine: maps directly to the equivalent `loop.run_in_executor()`. The idea is that people don't need an event loop to use the function:\r\n\r\n```python\r\nasync def coro():\r\n    await asyncio.run_in_executor(None, calculate, 100)\r\n```\r\n\r\n----\r\n\r\n`asyncio.run()` function: run a coroutine taking care of the asyncio event loop.\r\n\r\n**Pros:**\r\n\r\n* 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:\r\n\r\n    ```python\r\n    async def hello():\r\n        await asyncio.sleep(1)\r\n        print('hello')\r\n\r\n    loop = asyncio.get_event_loop()\r\n    loop.run_until_complete(hello())\r\n    loop.close()\r\n    ```\r\n\r\n    The problem is that the above snippet isn't fully correct, we should have a `try-finally` block to ensure that `loop.close()` is always called. Even in the docs we don't do that.  With `asyncio.run()` the snippet becomes much shorter:\r\n\r\n    ```python\r\n    async def hello():\r\n        await asyncio.sleep(1)\r\n        print('hello')\r\n\r\n    asyncio.run(hello())\r\n    ```\r\n\r\n* It's currently hard to experiment with asyncio in the REPL, because `get_event_loop` and `run_until_complete` are rather long names to type. With `asyncio.run()`:\r\n\r\n    ```pycon\r\n    >>> from asyncio import *\r\n    >>> async def foo():\r\n    ...     await sleep(1)\r\n    >>> run(foo())\r\n    ```\r\n\r\n    And `asyncio.run()` can be called multiple times.\r\n\r\n* 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.\r\n\r\n* The function promotes a coroutine-centric design.  In many cases, is is possible to bootstrap an asyncio program with just one coroutine.\r\n\r\n**Cons:**\r\n\r\n* It's not possible to completely substitute `loop.run_forever()`.  One of the documented patterns in asyncio is to use `run_forever` to bootstrap servers:\r\n\r\n    ```python\r\n    loop = asyncio.get_event_loop()\r\n    server = loop.run_until_complete(\r\n        asyncio.start_server(handle_echo, '127.0.0.1', 8888, loop=loop))\r\n\r\n    try:\r\n        loop.run_forever()\r\n    except KeyboardInterrupt:\r\n        pass\r\n\r\n    # Close the server\r\n    server.close()\r\n    loop.run_until_complete(server.wait_closed())\r\n    loop.close()\r\n    ```\r\n\r\n    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:\r\n\r\n    ```python\r\n    async def main():\r\n        server = await asyncio.start_server(handle_echo, '127.0.0.1', 8888)\r\n\r\n        try:\r\n            await asyncio.forever()\r\n        except KeyboardInterrupt:\r\n            pass\r\n\r\n        server.close()\r\n        await server.wait_closed()\r\n\r\n    asyncio.run(main())\r\n    ```\r\n\r\n    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).\r\n\r\n    However, we can probably add `asyncio.run()` function in 3.6 to cover some use cases, and enhance it further in 3.7.\r\n\r\n@gvanrossum, what do you think?"},"comment":{"url":"https://api.github.com/repos/python/asyncio/issues/comments/260734561","html_url":"https://github.com/python/asyncio/pull/465#issuecomment-260734561","issue_url":"https://api.github.com/repos/python/asyncio/issues/465","id":260734561,"user":{"login":"1st1","id":239003,"avatar_url":"https://avatars.githubusercontent.com/u/239003?v=3","gravatar_id":"","url":"https://api.github.com/users/1st1","html_url":"https://github.com/1st1","followers_url":"https://api.github.com/users/1st1/followers","following_url":"https://api.github.com/users/1st1/following{/other_user}","gists_url":"https://api.github.com/users/1st1/gists{/gist_id}","starred_url":"https://api.github.com/users/1st1/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/1st1/subscriptions","organizations_url":"https://api.github.com/users/1st1/orgs","repos_url":"https://api.github.com/users/1st1/repos","events_url":"https://api.github.com/users/1st1/events{/privacy}","received_events_url":"https://api.github.com/users/1st1/received_events","type":"User","site_admin":false},"created_at":"2016-11-15T19:03:51Z","updated_at":"2016-11-15T19:03:51Z","body":"@gvanrossum \r\n\r\n> -1. Something that's only meant to be used at the top level doesn't deserve\r\nto be a coroutine IMO.\r\n\r\nNormally yes, I agree. Although I don't think this argument fully applies to this particular use case.\r\n\r\nThe idea is to add APIs to make it possible to move the application bootstrap logic (the \"main\" function\") into a coroutine.  The design of `asyncio.start_server()` and `loop.create_server()` API requires you to use `loop.run_forever()` function to properly cleanup.\r\n\r\n`asyncio.run()` solves a lot of cases, but without something like `asyncio.forever()` it cannot help you when your application starts a server. And servers might be the majority of asyncio programs out there. Please take a look at the code example a couple of paragraphs below.\r\n\r\n> And even if we have forever() I think you should be able to get the same\r\nbehavior (relative to KeyboardInterrupt) with sleep(100000).\r\n\r\nI see your point. Unfortunately it's not possible to implement this behaviour in `asyncio.sleep`, so we'd definitely need a better name for `forever()`. \r\n\r\nWhat if we rename `forever()` to `interrupted()`:\r\n\r\n```python\r\nasync def main():\r\n    server = await asyncio.start_server(handle_echo, '127.0.0.1', 8888)\r\n    try:\r\n        await asyncio.interrupted()\r\n    finally:\r\n        server.close()\r\n        await server.wait_closed()\r\n\r\nasyncio.run(main())\r\n```\r\n\r\n---------\r\n\r\n@vxgmichel \r\n\r\n> * Isn't restricting asyncio.run() to the main thread a bit extreme? I guess it might help avoiding some issues though, e.g subprocess handling on unix systems.\r\n\r\nThe 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.\r\n\r\n> * About the clean-up, would it make sense to run the loop once, to get rid of possible pending callbacks? I remember I had to do that a few times to avoid some warnings.\r\n\r\nWell, we run `loop.shutdown_asyncgens()` before closing, isn't that enough? I don't want to add another props for that, as it's just masking bugs and lack of some APIs in asyncio.\r\n\r\n> * Shouldn't the current loop be saved and restored in the finally clause? The current implementation can lead to surprising results:\r\n\r\nCan't do that. `asyncio.get_event_loop` is broken with the default policy: \r\n\r\n* It will be raising a `RuntimeError(There is no current event loop in thread)` after first call to `asyncio.set_event_loop(None)`.\r\n\r\n* I have no idea if the event loop that `asyncio.get_event_loop` returns is new or it was used before. Restoring a loop that was just created doesn't make any sense.\r\n\r\n**I'm -1 on all three.**\r\n\r\n------\r\n\r\n> I'm not sure it makes sense to have an asynchronous equivalent to loop.run_forever.\r\n> * loop.run_forever: run until the loop is stopped\r\n> * await asyncio.forever: wait until the loop is stopped\r\n> But if the loop is stopped, how can the coroutine keep running?\r\n\r\nIt's up to the documentation -- the idea is that `forever()` wakes up when the loop is interrupted by a `BaseException` or was stopped by `loop.stop()`.  It then allows you to cleanup your resources. `wait_for_interrupt()` doesn't cover that -- it only covers `KeyboardInterrupt`.\r\n\r\nPlease study my example code in this comment and in https://github.com/python/asyncio/pull/465#issuecomment-260521057."}}
{"actor":{"display_login":"1st1"}}