Reading the Tornado documentation, it's very clear how to call an async function to return a response:
class GenAsyncHandler(RequestHandler):
@gen.coroutine
def get(self):
http_client = AsyncHTTPClient()
response = yield http_client.fetch("http://example.com")
do_something_with_response(response)
self.render("template.html")
What's lacking is how should a call be made asynchronously to a background task that has no relevance to the current request:
class GenAsyncHandler(RequestHandler):
@gen.coroutine
def _background_task():
pass # do lots of background stuff
@gen.coroutine
def get(self):
_dont_care = yield self._background_task()
self.render("template.html")
This code would be expected to work, except that it runs synchronously and the request waits on it until it's finished.
What is the right way to asynchronously call this task, while immediately returning the current request?
Update: Since Tornado 4.0 (July 2014), the below functionality is available in the IOLoop.spawn_callback method.
Unfortunately it's kind of tricky. You need to both detach the background task from the current request (so that a failure in the background task doesn't result in a random exception thrown into the request) and ensure that something is listening to the background task's result (to log its errors if nothing else). This means something like this:
Something like this will probably be added to tornado itself in the future.
I recommend using toro. It provides a relatively simple mechanism for setting up a background queue of tasks.
The following code (put in queue.py for example), starts a simple "worker()" that simply waits until there is something in his queue. If you call
queue.add(function,async,*args,**kwargs)
this adds an item to the queue which will wake up worker() which then kicks off the task.I added the async parameter so that this can support background tasks wrapped in @gen.coroutine and those without.
In your main tornado app:
And now you can schedule a back ground task quite simply:
I have a time-consuming task in post request, maybe more than 30 minutes need, but client required return a result immediately.
First, I used IOLoop.current().spawn_callback. It works! but! If the first request task is running, second request task blocked! Because all tasks are in main event loop when use spawn_callback, so one task is synchronous execution, other tasks blocked.
Last, I use tornado.concurrent. Example:
and visit http://127.0.0.1:8000, you can see it's run ok:
Want to help everyone!
Simply do:
The
_background_task
coroutine returns aFuture
which is unresolved until the coroutine completes. If you don't yield theFuture
, and instead simply execute the next line immediately, thenget()
doesn't wait for_background_task
to finish.An interesting detail is that, until
_background_task
finishes, it maintains a reference toself
. (Don't forget to addself
as a parameter, by the way.) Your RequestHandler won't be garbage collected until after_background_task
completes.