How to understand Tornado gen.coroutine
Categories:
Understanding Tornado's gen.coroutine
for Asynchronous Programming

Explore how Tornado's gen.coroutine
decorator simplifies asynchronous programming in Python, enabling efficient handling of I/O-bound operations without callback hell.
Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed. It's known for its ability to handle a large number of concurrent connections, making it ideal for long-polling, WebSockets, and other applications requiring a long-lived connection to each user. A core component of Tornado's asynchronous capabilities is the tornado.gen
module, particularly the gen.coroutine
decorator, which allows you to write asynchronous code in a synchronous style.
The Challenge of Asynchronous Programming
Traditionally, asynchronous programming in Python often involved complex callback chains, leading to what's colloquially known as 'callback hell.' This pattern makes code difficult to read, debug, and maintain. Consider a scenario where you need to make multiple I/O calls (e.g., database queries, HTTP requests) in sequence, where each call depends on the result of the previous one. Without proper asynchronous constructs, this can block the event loop, severely impacting performance.

Visualizing 'Callback Hell' without gen.coroutine
Introducing gen.coroutine
The tornado.gen.coroutine
decorator transforms a generator function into a coroutine. When you yield
a Future
object (or a tornado.gen.Task
), the coroutine pauses execution until the Future
is resolved. Once the Future
completes, its result is sent back to the generator, and execution resumes from where it left off. This allows you to write sequential-looking code that is actually non-blocking and asynchronous.
import tornado.gen
import tornado.ioloop
import tornado.httpclient
@tornado.gen.coroutine
def fetch_urls_sequentially(urls):
http_client = tornado.httpclient.AsyncHTTPClient()
results = []
for url in urls:
print(f"Fetching {url}...")
response = yield http_client.fetch(url)
results.append(response.body.decode()[:50]) # Take first 50 chars
print(f"Finished {url}")
return results
@tornado.gen.coroutine
def main():
urls = [
"http://www.google.com",
"http://www.bing.com",
"http://www.yahoo.com"
]
fetched_content = yield fetch_urls_sequentially(urls)
print("\n--- Fetched Content Samples ---")
for i, content in enumerate(fetched_content):
print(f"URL {i+1}: {content}...")
if __name__ == "__main__":
print("Starting Tornado IOLoop...")
tornado.ioloop.IOLoop.current().run_sync(main)
print("Tornado IOLoop finished.")
Example of gen.coroutine
for sequential asynchronous HTTP fetches.
In the example above, fetch_urls_sequentially
is a generator function decorated with @tornado.gen.coroutine
. When yield http_client.fetch(url)
is encountered, the function pauses, allowing the Tornado IOLoop to process other events. Once the HTTP request completes, the response
is sent back to the generator, and the loop continues to the next URL. This makes the code much cleaner than a callback-based approach.
gen.coroutine
is powerful, modern Python (3.5+) offers async
/await
keywords, which provide a more native and often preferred way to write asynchronous code. Tornado fully supports async
/await
, and gen.coroutine
can be seen as a precursor or an alternative for older Python versions or specific use cases.How gen.coroutine
Works Internally
When a function is decorated with @gen.coroutine
, Tornado wraps it in a special runner. This runner iterates through the generator. When it encounters a yield
expression, it expects a Future
object. The runner then registers a callback with this Future
. When the Future
completes (either successfully or with an error), the callback is invoked, and the runner sends the result (or raises the exception) back into the generator using generator.send()
or generator.throw()
, effectively resuming its execution.

Internal Mechanism of gen.coroutine
Error Handling and Concurrency
Error handling within gen.coroutine
functions is straightforward, as try...except
blocks work as expected. If a Future
yields an exception, it will be re-raised at the yield
point. For concurrent execution of multiple asynchronous operations, tornado.gen.multi
(or asyncio.gather
with async
/await
) can be used to wait for several Future
objects to complete simultaneously.
import tornado.gen
import tornado.ioloop
import tornado.httpclient
@tornado.gen.coroutine
def fetch_url(url):
http_client = tornado.httpclient.AsyncHTTPClient()
try:
print(f"Attempting to fetch {url}...")
response = yield http_client.fetch(url)
print(f"Successfully fetched {url}")
return response.body.decode()[:50]
except tornado.httpclient.HTTPError as e:
print(f"Error fetching {url}: {e}")
return f"Error: {e.code}"
except Exception as e:
print(f"An unexpected error occurred for {url}: {e}")
return f"Error: {e}"
@tornado.gen.coroutine
def fetch_multiple_concurrently(urls):
print("\n--- Fetching URLs Concurrently ---")
futures = [fetch_url(url) for url in urls]
# Wait for all futures to complete
results = yield tornado.gen.multi(futures)
return results
@tornado.gen.coroutine
def main_concurrent():
urls = [
"http://www.google.com",
"http://www.nonexistent-domain-12345.com", # This will cause an error
"http://www.bing.com"
]
fetched_content = yield fetch_multiple_concurrently(urls)
print("\n--- Concurrent Fetched Content Samples ---")
for i, content in enumerate(fetched_content):
print(f"Result {i+1}: {content}...")
if __name__ == "__main__":
print("Starting Tornado IOLoop for concurrent fetches...")
tornado.ioloop.IOLoop.current().run_sync(main_concurrent)
print("Tornado IOLoop finished concurrent fetches.")
Example of error handling and concurrent fetches using gen.multi
.
gen.coroutine
with async
/await
in the same function is generally discouraged. While Tornado provides compatibility layers, it's best to stick to one style within a given coroutine for clarity and to avoid potential subtle issues. For new code, async
/await
is the recommended approach.