Как понимать асинхронность в Tornado?
tornado.gen.coroutine + yield != асинхронность. Вызовы, которые делаются должны уметь работать асинхронно. Смотрим здесь первый пример: class AsyncHandler(RequestHandler):
    @asynchronous
    def get(self):
        http_client = AsyncHTTPClient()
        http_client.fetch("http://example.com",
                          callback=self.on_fetch)

    def on_fetch(self, response):
        do_something_with_response(response)
        self.render("template.html")
Метод fetch объекта класса AsyncHTTPClient -- асинхронный. На это указывает аргумент callback (и название класса, конечно :)). Когда страница будет получена, вызовется _on_fetch. Как и в AJAX.
Пример ниже на этой же странице -- это переписанная версия того же самого, просто красивее, без лапши коллбеков. Для этого и нужна пара tornado.gen.coroutine + yield.
Вызов time.sleep(2) блокирует весь ioloop, вместо него можно воспользоваться чем-то типа yield tornado.gen.Task(tornado.ioloop.IOLoop.current().add_timeout, time.time() + 2)
Но даже заменив эту строку, асинхронности не будет заметна. Это связано с ограничениями браузеров: они не умеют одновременно открывать одну и туже вкладку. Поэтому нужно открыть вкладки в разных браузерах.
Еще одно: декоратор @tornado.web.asynchronous не нужен при использовании @tornado.gen.coroutine.
Вот эту страничку нужно обязательно прочитать.
Лог сервера при одновременном запуске двух клиентов:[I 150218 17:38:41 test:19] outer starts
[I 150218 17:38:43 test:29] inner runs
[I 150218 17:38:43 test:21] some text here
[I 150218 17:38:45 test:29] inner runs
[I 150218 17:38:45 test:23] outer ends
[I 150218 17:38:45 web:1825] 304 GET / (127.0.0.1) 4005.73ms
[I 150218 17:38:45 test:19] outer starts
[I 150218 17:38:47 test:29] inner runs
[I 150218 17:38:47 test:21] some text here
[I 150218 17:38:49 test:29] inner runs
[I 150218 17:38:49 test:23] outer ends
[I 150218 17:38:49 web:1825] 304 GET / (127.0.0.1) 4004.74ms