[Python-ideas] The async API of the future: Some thoughts from an ignorant Tornado user
Ben Darnell
ben at bendarnell.com
Mon Oct 15 00:19:27 CEST 2012
On Sat, Oct 13, 2012 at 3:27 PM, Daniel McDougall
<daniel.mcdougall at liftoffsoftware.com> wrote:
> (This is a response to GVR's Google+ post asking for ideas; I
> apologize in advance if I come off as an ignorant programming newbie)
>
> I am the author of Gate One (https://github.com/liftoff/GateOne/)
> which makes extensive use of Tornado's asynchronous capabilities. It
> also uses multiprocessing and threading to a lesser extent. The
> biggest issue I've had trying to write asynchronous code for Gate One
> is complexity. Complexity creates problems with expressiveness which
> results in code that, to me, feels un-Pythonic. For evidence of this
> I present the following example: The retrieve_log_playback()
> function: http://bit.ly/W532m6 (link goes to Github)
>
> All the function does is generate and return (to the client browser)
> an HTML playback of their terminal session recording. To do it
> efficiently without blocking the event loop or slowing down all other
> connected clients required loads of complexity (or maybe I'm just
> ignorant of "a better way"--feel free to enlighten me). In an ideal
> world I could have just done something like this:
>
> import async # The API of the future ;)
> async.async_call(retrieve_log_playback, settings, tws,
> mechanism=multiprocessing)
> # tws == instance of tornado.web.WebSocketHandler that holds the open connection
What you've described is very similar the the
concurrent.futures.Executor.submit() method. ProcessPoolExecutor
still has multiprocessing's pickle-related limitations, but other than
that you're free to create ProcessPoolExecutors and/or
ThreadPoolExecutors and submit work to them. Your
retrieve_log_playback function could become:
# create a global/singleton ProcessPoolExecutor
executor = concurrent.futures.ProcessPoolExecutor()
def retrieve_log_playback(settings, tws=None):
# set up settings dict just like the original
io_loop = tornado.ioloop.IOLoop.instance()
future = executor.submit(_retrieve_log_playback, settings)
def send_message(future):
tws.write_message(future.result())
future.add_done_callback(lambda future: io_loop.add_callback(send_message)
In Tornado 3.0 there will be some native support for Futures - the
last line will probably become "io_loop.add_future(future,
send_message)". In _retrieve_log_playback you no longer have a queue
argument, and instead just return the result normally.
It's also possible to do this just using multiprocessing instead of
concurrent.futures - see multiprocessing.Pool.apply_async.
-Ben
More information about the Python-ideas
mailing list