self.render("template.html")
`Task` works with any function that takes a ``callback`` keyword argument
-(and runs that callback with zero or one arguments). For more complicated
-interfaces, `Task` can be split into two parts: `Callback` and `Wait`::
+(and runs that callback with zero or one arguments). You can also yield
+a list of ``Tasks``, which will be started at the same time and run in parallel;
+a list of results will be returned when they are all finished::
+
+ def get(self):
+ http_client = AsyncHTTPClient()
+ response1, response2 = yield [gen.Task(http_client.fetch, url1),
+ gen.Task(http_client.fetch, url2)]
+
+For more complicated interfaces, `Task` can be split into two parts:
+`Callback` and `Wait`::
class GenAsyncHandler2(RequestHandler):
@asynchronous
self.render("template.html")
The ``key`` argument to `Callback` and `Wait` allows for multiple
-asynchronous operations to proceed in parallel: yield several
-callbacks with different keys, then wait for them once all the async
-operations have started.
+asynchronous operations to be started at different times and proceed
+in parallel: yield several callbacks with different keys, then wait
+for them once all the async operations have started.
"""
import functools
The argument is a sequence of `Callback` keys, and the result is
a list of results in the same order.
+
+ `WaitAll` is equivalent to yielding a list of `Wait` objects.
"""
def __init__(self, keys):
self.keys = keys
def callback(self, arg=None):
self.runner.set_result(self.key, arg)
+class Multi(YieldPoint):
+ """Runs multiple asynchronous operations in parallel.
+
+ Takes a list of ``Tasks`` or other ``YieldPoints`` and returns a list of
+ their responses. It is not necessary to call `Multi` explicitly,
+ since the engine will do so automatically when the generator yields
+ a list of ``YieldPoints``.
+ """
+ def __init__(self, children):
+ assert all(isinstance(i, YieldPoint) for i in children)
+ self.children = children
+
+ def start(self, runner):
+ for i in self.children:
+ i.start(runner)
+
+ def is_ready(self):
+ return all(i.is_ready() for i in self.children)
+
+ def get_result(self):
+ return [i.get_result() for i in self.children]
+
class _NullYieldPoint(YieldPoint):
def start(self, runner):
pass
except Exception:
self.finished = True
raise
+ if isinstance(yielded, list):
+ yielded = Multi(yielded)
if isinstance(yielded, YieldPoint):
self.yield_point = yielded
self.yield_point.start(self)
+import functools
from tornado.escape import url_escape
from tornado.httpclient import AsyncHTTPClient
from tornado.testing import AsyncHTTPTestCase, AsyncTestCase, LogTrapTestCase
f()
self.wait()
+ def delay_callback(self, iterations, callback, arg):
+ """Runs callback(arg) after a number of IOLoop iterations."""
+ if iterations == 0:
+ callback(arg)
+ else:
+ self.io_loop.add_callback(functools.partial(
+ self.delay_callback, iterations - 1, callback, arg))
+
def test_no_yield(self):
@gen.engine
def f():
pass
self.orphaned_callback()
+ def test_multi(self):
+ @gen.engine
+ def f():
+ (yield gen.Callback("k1"))("v1")
+ (yield gen.Callback("k2"))("v2")
+ results = yield [gen.Wait("k1"), gen.Wait("k2")]
+ self.assertEqual(results, ["v1", "v2"])
+ self.stop()
+ self.run_gen(f)
+
+ def test_multi_delayed(self):
+ @gen.engine
+ def f():
+ # callbacks run at different times
+ responses = yield [
+ gen.Task(self.delay_callback, 3, arg="v1"),
+ gen.Task(self.delay_callback, 1, arg="v2"),
+ ]
+ self.assertEqual(responses, ["v1", "v2"])
+ self.stop()
+ self.run_gen(f)
+
class GenSequenceHandler(RequestHandler):
@asynchronous