From: Ben Darnell Date: Sun, 11 Sep 2011 22:09:25 +0000 (-0700) Subject: Add support for lists of YieldPoints in the gen framework. X-Git-Tag: v2.1.0~15 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=94b483efc4b20e38ac250d96cb484150ddb0768b;p=thirdparty%2Ftornado.git Add support for lists of YieldPoints in the gen framework. --- diff --git a/tornado/gen.py b/tornado/gen.py index 6571e5ed8..900f61a7d 100644 --- a/tornado/gen.py +++ b/tornado/gen.py @@ -28,8 +28,17 @@ could be written with ``gen`` as:: 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 @@ -43,9 +52,9 @@ interfaces, `Task` can be split into two parts: `Callback` and `Wait`:: 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 @@ -149,6 +158,8 @@ class WaitAll(YieldPoint): 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 @@ -198,6 +209,28 @@ class Task(YieldPoint): 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 @@ -275,6 +308,8 @@ class Runner(object): 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) diff --git a/tornado/test/gen_test.py b/tornado/test/gen_test.py index 49cf978a2..bdfe97dec 100644 --- a/tornado/test/gen_test.py +++ b/tornado/test/gen_test.py @@ -1,3 +1,4 @@ +import functools from tornado.escape import url_escape from tornado.httpclient import AsyncHTTPClient from tornado.testing import AsyncHTTPTestCase, AsyncTestCase, LogTrapTestCase @@ -11,6 +12,14 @@ class GenTest(AsyncTestCase): 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(): @@ -160,6 +169,28 @@ class GenTest(AsyncTestCase): 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 diff --git a/website/sphinx/conf.py b/website/sphinx/conf.py index ac0b61dc0..905ab4102 100644 --- a/website/sphinx/conf.py +++ b/website/sphinx/conf.py @@ -30,6 +30,7 @@ coverage_ignore_modules = [ # I wish this could go in a per-module file... coverage_ignore_classes = [ # tornado.gen + "Multi", "Runner", "YieldPoint",