Post

What is asyncio's ensure_future?

tl;dr ensure_future let's us execute a coroutine in the background, without explicitly waiting for it to finish. If we need, we can wait for it later or poll for result. In other words, this is a way of executing code in asyncio without await.

Asynchronous Python code written with asyncio remains pretty straightforward when one uses async/await keywords, because a) code resembles synchronous b) any switch to another coroutine is explicit.

# synchronous version
def tweet_about_latest_article(feed_url):
    response = requests.get(feed_url)  # make HTTP request
    assert response.ok
    articles = extract_articles_from_raw_xml_string(response.text)
    tweet(articles[0])  # call Twitter's API

# asynchronous version
async def tweet_about_latest_article(feed_url):
    with aiohttp.ClientSession() as session:
        async with session.get(feed_url) as response:
            assert response.status == 200
            contents = await response.read()  # make HTTP request

    articles = extract_articles_from_raw_xml_string(contents)
    await tweet(articles[0])  # call Twitter's API

You can still read code from top to bottom and understand the flow. However, in async code a lot is happening under the hood. Whenever interpreter encounters await keyword, it may switch to another coroutine. So await is what makes event loop to check on whatever pending tasks are there and resume execution for one of them (Python is still single-threaded with asyncio!).

What is asyncio's coroutine?

A coroutine used to be an overloaded term, but for the sake of the article let's simplify a definition. async function is piece of code that can run concurrently (not in parallel!) and whose execution can be suspended while awaiting for other coroutines to finish.

Do I have to wait?

It is not always desired for an async function to await for another coroutine, especially if it does not return any result. Consider example function from the beginning - tweet_about_latest_article. In the end, we call Twitter's API and we don't actually need its result. What if we don't have to wait? Let's wrap a call with asyncio.Task factory - asyncio.ensure_future.

What is a Task in asyncio?

One can think of Task as a single execution unit in asyncio. Exactly one Task is executed at any moment. Direct instantiation of a Task is discouraged - that's what asyncio.ensure_future is for. Since Python3.7 we can also use asyncio.create_task.

async def tweet_about_latest_article(feed_url):
    with aiohttp.ClientSession() as session:
        async with session.get(feed_url) as response:
            assert response.status == 200
            contents = await response.read()  # we need to wait for that

    articles = extract_articles_from_raw_xml_string(contents)
    asyncio.ensure_future(tweet(articles[0]))  # this can be done in background

What if I want to run blocking code in background?

Check out asyncio's executors! Now that you know what ensure_future is about, you can wrap loop.run_in_executor calls with asyncio.ensure_future. Also, don't forget to check which executor is the best for your use case.

This post is licensed under CC BY 4.0 by the author.

Comments powered by Disqus.