Synchronous v/s Asynchronous

phraniiac picture phraniiac · Jun 26, 2015 · Viewed 15.5k times · Source

I am trying to understand the basic example provided on the introduction page of tornado documentation. It has 2 blocks of code. The Synchronous one is fine for me, and I do understand it. But the asynchronous one is one I am not able to understand.

Synchronous

from tornado.httpclient import HTTPClient

def synchronous_fetch(url):
    http_client = HTTPClient()
    response = http_client.fetch(url)
    return response.body

Asynchronous

from tornado.httpclient import AsyncHTTPClient

def asynchronous_fetch(url, callback):
    http_client = AsyncHTTPClient()
    def handle_response(response):
        callback(response.body)
    http_client.fetch(url, callback=handle_response)

If you can provide with a better example, please do so.

Answer

BorrajaX picture BorrajaX · Jul 4, 2015

The idea of asynchronous calls is something that works pretty much the same in many web related programming... "stuff" (frameworks, servers, libraries...) Is not only a concept from the Tornado web server.

The basic idea is:

  • On a s̲y̲n̲c̲h̲r̲o̲n̲o̲u̲s̲ request, you make the request and stop executing your program until you get a response from the HTTP server (or an error if the server can't be reached, or a timeout if the sever is taking way, way too long to reply) The interpreter is blocked until the request is completed (until you got a definitive answer of what happened with the request: did it go well? was there an error? a timeout?... ).
  • On a̲s̲y̲n̲c̲h̲r̲o̲n̲o̲u̲s̲ requests, you "launch" the request, and you kind of "forget about it", meaning: The interpreter continues executing the code after the request is made without waiting for the request to be completed.

    This seems... rather pointless, right? You send the request "to the void of space", and continue executing as usual? What happens when the server sends you its response? I made a request, and I wanna know what happened to it! Otherwise, I wouldn't have typed that in my code to begin with!!

    Well, here's where the callback comes in. You launch the request "to the void of space" BUT you provide a callback function so when the HTTP server on the other end sends you its response, that function is run with said response as the first argument.

Let's see it with a en example.

I've created a very simple Tornado server (using Python 2.7 and Tornado 4.2) with only one handler. On a GET, it takes 5 seconds to return. I've done that with a time.sleep, but in real life, it could be a very time consuming process (access a database, perform some calculations... who knows?...)

Here's the server file (based on the example provided in the Tornado documentation):

SampleServer.py:

#!/usr/bin/env python2.7
import time
import tornado.ioloop
import tornado.web


class MainHandler(tornado.web.RequestHandler):
    def get(self):
        print "Someone is GET'ing me"
        time.sleep(5)
        self.write("Hello, world")

application = tornado.web.Application([
        (r"/", MainHandler),
    ])

if __name__ == "__main__":
    application.listen(8888)
    print "Starting sample server."
    tornado.ioloop.IOLoop.current().start()

Open a terminal and run that code to have a server. You'll get a Tornado listening on port 8888 of your local machine.

Now, let's create another script (which you'll have to run in another terminal) that GETs http://localhost:8888 in two ways: First synchronously and then asynchronously.

SampleFetcher.py:

#!/usr/bin/env python2.7
import datetime
import tornado.ioloop
from tornado.httpclient import HTTPClient, AsyncHTTPClient


def HUMAN_DT_NOW():
    return datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S")


def synchronous_fetch(url):
    http_client = HTTPClient()
    start_dt = datetime.datetime.now()
    response = http_client.fetch(url)
    end_dt = datetime.datetime.now()
    print ("The synchronous fetch took %s seconds."
           % (end_dt - start_dt).total_seconds())
    print "(Sync) Server said: \"%s\"" % response.body


def asynchronous_fetch(url):
    http_client = AsyncHTTPClient()

    def handle_response(response):
        print ""
        print "Yawwza... Finally!!!."
        print "The time now is %s" % HUMAN_DT_NOW()
        print "(Async) Server said: \"%s\"" % response.body
    print "Gonna launch a 'fetch' to the universe at %s..." % HUMAN_DT_NOW()
    http_client.fetch(url, callback=handle_response)

if __name__ == "__main__":
    print " ------ Synchronous ------ "
    print ("Starting synchronous fetch at %s."
           " The program will block for about 5 secs." % HUMAN_DT_NOW())
    synchronous_fetch('http://localhost:8888')
    print "Pfew! That was a lot of wait time!!. I got bored watching my terminal"
    print ""
    print "Aight, let's see what Asynchronous can do"
    print " ------ Asynchronous ------ "
    asynchronous_fetch('http://localhost:8888')
    print "You're gonna see this line before the \"Yawwza...\" one"
    print "This one too. Now is %s" % HUMAN_DT_NOW()
    # The IOLoop below is required to prevent the script from closing ahead
    # of time, but allowing Asynchronous interactions
    tornado.ioloop.IOLoop.current().start()

This will output:

Starting synchronous fetch at 2015/07/04 13:25:47. The program will block for about 5 secs.
The synchronous fetch took 5.009597 seconds.
(Sync) Server said: "Hello, world"
Pfew! That was a lot of wait time!!. I got bored watching my terminal

Aight, let's see what Asynchronous can do
 ------ Asynchronous ------ 
Gonna launch a 'fetch' to the universe at 2015/07/04 13:25:52...
You're gonna see this line before the "Yawwza..." one
This one too. Now is 2015/07/04 13:25:52

Yawwza... Finally!!!.
The time now is 2015/07/04 13:25:57
(Async) Server said: "Hello, world"

Let's focus on the asynchronous part, here:

 ------ Asynchronous ------ 
Gonna launch a 'fetch' to the universe at 2015/07/04 13:25:52...
You're gonna see this line before the "Yawwza..." one
This one too. Now is 2015/07/04 13:25:52

Yawwza... Finally!!!.
The time now is 2015/07/04 13:25:57
(Async) Server said: "Hello, world"

If you see, the script made the asynchronous_fetch at 13:25:52, but immediately (in the same second), the interpreter continued executing, and run the next statements after the request was made (the lines that print You're gonna see this line before the "Yawwza..." one and This one too. Now is 2015/07/04 13:25:52 ).

Then, around 5 seconds later, the server responded, and the callback function (which was handle_response) was executed, and that's when you see

Yawwza... Finally!!!.
The time now is 2015/07/04 13:25:57

I hope this helped understanding the idea a bit. It's a very useful concept, and it doesn't only apply to Tornado.

Feel free to play with the two sample scripts provided, change stuff, increase the times it gets for the server to reply...

Further recommended reading: