StevieP
StevieP

Reputation: 1629

Why don't two sequential coroutines (async functions) execute in parallel?

So, I'm just trying to wrap my head around async programming (in particular the Tornado framework), and thought I'd start with the basics: calling "awaiting" on two coroutines:

from tornado.ioloop import IOLoop
from tornado.web import Application, url, RequestHandler
from tornado.gen import sleep

class TestHandler(RequestHandler):    
    async def get(self):
        f1 = await self.test("f1")
        f2 = await self.test("f2")
        self.write(f1 + " " + f2)

    async def test(self, msg):
        for i in range(5):
            print(i)
            await sleep(1) # this is tornado's async sleep
        return msg

app = Application([url(r'/', TestHandler)], debug=True)
app.listen(8080)
ioloop = IOLoop.current()
ioloop.start()

The issue, however, is that when I hit localhost:8080 in my browser, and stare at my python console, I don't see two interwoven sequences of 0 1 2 3 4, but two sequential sequences...

I've read the Tornado FAQ over-and-over again and can't seem to understand what I'm doing wrong.

Upvotes: 0

Views: 817

Answers (2)

Milovan Tomašević
Milovan Tomašević

Reputation: 8673

Parallelism - official example

The multi function accepts lists and dicts whose values are Futures, and waits for all of those Futures in parallel:

from tornado.gen import multi

async def parallel_fetch(url1, url2):
    resp1, resp2 = await multi([http_client.fetch(url1),
                                http_client.fetch(url2)])

async def parallel_fetch_many(urls):
    responses = await multi ([http_client.fetch(url) for url in urls])
    # responses is a list of HTTPResponses in the same order

async def parallel_fetch_dict(urls):
    responses = await multi({url: http_client.fetch(url)
                             for url in urls})
    # responses is a dict {url: HTTPResponse}

Upvotes: 0

Ben Darnell
Ben Darnell

Reputation: 22154

This runs f1, waits for it to finish, then runs f2:

    f1 = await self.test("f1")
    f2 = await self.test("f2")

To run things in parallel, you can't await the first one before starting the second. The simplest way to do this is to do them both in one await:

f1, f2 = await tornado.gen.multi(self.test("f1"), self.test("f2"))

Or in advanced cases, you can start f1 without waiting for it then come back to wait for it later:

f1_future = tornado.gen.convert_yielded(self.test("f1"))
f2_future = tornado.gen.convert_yielded(self.test("f2"))
f1 = await f1_future
f2 = await f2_future

Upvotes: 3

Related Questions