concurrent futures, async futures and await

Ian Kelly ian.g.kelly at gmail.com
Thu Feb 23 13:33:15 EST 2017


On Thu, Feb 23, 2017 at 9:44 AM, Nagy László Zsolt <gandalf at shopzeus.com> wrote:
>
>> My guess: because asyncio wouldn't know what to do with a
>> concurrent.futures.Future.
> I see that as a problem.
>>
>> The tornado docs say that "You can also use
>> tornado.gen.convert_yielded to convert anything that would work with
>> yield into a form that will work with await":
>> http://www.tornadoweb.org/en/stable/guide/coroutines.html#python-3-5-async-and-await
> It is true if you yield it from a normal tornado.gen.coroutine, and then
> let tornado's own ioloop handle it. But if you try to refactor to a
> native coroutine and await for it (instead of yield), then it will stop
> working. It is because the await statement is not implemented by
> tornado. It is implemented in core Python, and it does not support a
> concurrent futures. But hey, concurrent futures are part of the standard
> library, so they *should* work together.
>
> Here is an example that makes the problem clear.
>
> This one works:
>
> executor = ThreadPoolExecutor(4)
>
> @tornado.gen.coroutine
> def produce_chunks(filename, chunk_size=8192):
>         with open(filename,"rb") as fin:
>             chunk = yield executor.submit(f.read, chunk_size)
>             process_chunk(chunk)
>
>
> It is because "yield executor.submit" will yield the concurrent future
> to the ioloop's handler, and tornado's ioloop is clever enough to detect
> it, and wrap it in a thread-safe way.
>
> But this won't work:
>
> async def produce_chunks(filename, chunk_size=8192):
>     with open(filename,"rb") as fin:
>         chunk = await executor.submit(f.read, chunk_size)
>         process_chunk(chunk)
>
>
> Simply because the concurrent future returned by executor.submit does
> not implement __await__ and so it cannot be awaited for.

I get that, but what happens if you try wrapping the executor.submit
call with tornado.gen.convert_yielded as the tornado docs suggest and
as I suggested above?

> Right now asyncio does not know how to handle this. But I think it
> should, because it is part of the standard library, and there are very
> important use cases (like the one above) that cannot be implemented
> without concurrent futures. In the above example, file.read will almost
> certainly block the execution, and there is no platform independent way
> of doing an async file read operation other than doing it in a different
> thread. AFAIK async file reads are not supported in the Linux kernel,
> and the only way to do this is to use a thread.
>
> Of course, asyncio should not care if the executor is doing the task in
> a different thread or a different process. All I'm saying is that
> concurrent.futures.Future should implement the __await__ method, and
> asyncio should be able to use it.

I found this in the original PEP at
http://legacy.python.org/dev/peps/pep-3156/#futures:

"""
In the future (pun intended) we may unify asyncio.Future and
concurrent.futures.Future, e.g. by adding an __iter__() method to the
latter that works with yield from. To prevent accidentally blocking
the event loop by calling e.g. result() on a Future that's not done
yet, the blocking operation may detect that an event loop is active in
the current thread and raise an exception instead. However the current
PEP strives to have no dependencies beyond Python 3.3, so changes to
concurrent.futures.Future are off the table for now.
"""

Maybe we're now far enough into the future that this could be
reconsidered. In the meantime, the user does have other options:

1) For tornado, use tornado.gen.convert_yielded.

2) For asyncio, create an asyncio future and link them by setting a
callback on the concurrent future that propagates the result (using
call_soon_threadsafe since the callback may run in another thread).

3) Better, don't use concurrent.futures directly in the first place;
instead use https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.AbstractEventLoop.run_in_executor
which runs a function in an Executor and wraps it up via #2
transparently.



More information about the Python-list mailing list