]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
Add docs about calling coroutines.
authorBen Darnell <ben@bendarnell.com>
Sun, 2 Aug 2015 20:17:31 +0000 (16:17 -0400)
committerBen Darnell <ben@bendarnell.com>
Sun, 2 Aug 2015 20:24:36 +0000 (16:24 -0400)
Show an alternative to PeriodicCallback.

Closes #1463.

docs/guide/coroutines.rst

index a3fed1fb2b090fa1a8d761a9a7044c6be511879e..f5932e7623c04c7b1ba74e3c5c90f69cb9123e7a 100644 (file)
@@ -61,6 +61,55 @@ and sends the result back into the generator as the result of the
 class directly except to immediately pass the `.Future` returned by
 an asynchronous function to a ``yield`` expression.
 
+How to call a coroutine
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Coroutines do not raise exceptions in the normal way: any exception
+they raise will be trapped in the `.Future` until it is yielded. This
+means it is important to call coroutines in the right way, or you may
+have errors that go unnoticed::
+
+    @gen.coroutine
+    def divide(x, y):
+        return x / y
+
+    def bad_call():
+        # This should raise a ZeroDivisionError, but it won't because
+        # the coroutine is called incorrectly.
+        divide(1, 0)
+
+In nearly all cases, any function that calls a coroutine must be a
+coroutine itself, and use the ``yield`` keyword in the call. When you
+are overriding a method defined in a superclass, consult the
+documentation to see if coroutines are allowed (the documentation
+should say that the method "may be a coroutine" or "may return a
+`.Future`")::
+
+    @gen.coroutine
+    def good_call():
+        # yield will unwrap the Future returned by divide() and raise
+        # the exception.
+        yield divide(1, 0)
+
+Sometimes you may want to "fire and forget" a coroutine without waiting
+for its result. In this case it is recommended to use `.IOLoop.spawn_callback`,
+which makes the `.IOLoop` responsible for the call. If it fails,
+the `.IOLoop` will log a stack trace::
+
+    # The IOLoop will catch the exception and print a stack trace in
+    # the logs. Note that this doesn't look like a normal call, since
+    # we pass the function object to be called by the IOLoop.
+    IOLoop.current().spawn_callback(divide, 1, 0)
+
+Finally, at the top level of a program, *if the `.IOLoop` is not yet
+running,* you can start the `.IOLoop`, run the coroutine, and then
+stop the `.IOLoop` with the `.IOLoop.run_sync` method. This is often
+used to start the ``main`` function of a batch-oriented program::
+
+    # run_sync() doesn't take arguments, so we must wrap the
+    # call in a lambda.
+    IOLoop.current().run_sync(lambda: divide(1, 0))
+
 Coroutine patterns
 ~~~~~~~~~~~~~~~~~~
 
@@ -161,3 +210,32 @@ from `Motor <http://motor.readthedocs.org/en/stable/>`_::
         cursor = db.collection.find()
         while (yield cursor.fetch_next):
             doc = cursor.next_object()
+
+Running in the background
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+`.PeriodicCallback` is not normally used with coroutines. Instead, a
+coroutine can contain a ``while True:`` loop and use
+`tornado.gen.sleep`::
+
+    @gen.coroutine
+    def minute_loop():
+        while True:
+            yield do_something()
+            yield gen.sleep(60)
+
+    # Coroutines that loop forever are generally started with
+    # spawn_callback().
+    IOLoop.current().spawn_callback(minute_loop)
+
+Sometimes a more complicated loop may be desirable. For example, the
+previous loop runs every ``60+N`` seconds, where ``N`` is the running
+time of ``do_something()``. To run exactly every 60 seconds, use the
+interleaving pattern from above::
+
+    @gen.coroutine
+    def minute_loop2():
+        while True:
+            nxt = gen.sleep(60)   # Start the clock.
+            yield do_something()  # Run while the clock is ticking.
+            yield nxt             # Wait for the timer to run out.