diff --git a/docs/faq.rst b/docs/faq.rst new file mode 100644 index 00000000..d45173c8 --- /dev/null +++ b/docs/faq.rst @@ -0,0 +1,96 @@ +Frequently Asked Questions +========================== + +.. contents:: + :local: + +Why isn't this example with ``time.sleep()`` running in parallel? +----------------------------------------------------------------- + +Many people's first foray into Tornado's concurrency looks something like +this:: + + class BadExampleHandler(RequestHandler): + def get(self): + for i in range(5): + print(i) + time.sleep(1) + +Fetch this handler twice at the same time and you'll see that the second +five-second countdown doesn't start until the first one has completely +finished. The reason for this is that `time.sleep` is a **blocking** +function: it doesn't allow control to return to the `.IOLoop` so that other +handlers can be run. + +Of course, `time.sleep` is really just a placeholder in these examples, +the point is to show what happens when something in a handler gets slow. +No matter what the real code is doing, to achieve concurrency blocking +code must be replaced with non-blocking equivalents. This means one of three things: + +1. *Find a coroutine-friendly equivalent.* For `time.sleep`, use + `tornado.gen.sleep` instead:: + + class CoroutineSleepHandler(RequestHandler): + @gen.coroutine + def get(self): + for i in range(5): + print(i) + yield gen.sleep(1) + + When this option is available, it is usually the best approach. + See the `Tornado wiki `_ + for links to asynchronous libraries that may be useful. + +2. *Find a callback-based equivalent.* Similar to the first option, + callback-based libraries are available for many tasks, although they + are slightly more complicated to use than a library designed for + coroutines. These are typically used with `tornado.gen.Task` as an + adapter:: + + class CoroutineTimeoutHandler(RequestHandler): + @gen.coroutine + def get(self): + io_loop = IOLoop.current() + for i in range(5): + print(i) + yield gen.Task(io_loop.add_timeout, io_loop.time() + 1) + + Again, the + `Tornado wiki `_ + can be useful to find suitable libraries. + +3. *Run the blocking code on another thread.* When asynchronous libraries + are not available, `concurrent.futures.ThreadPoolExecutor` can be used + to run any blocking code on another thread. This is a universal solution + that can be used for any blocking function whether an asynchronous + counterpart exists or not:: + + executor = concurrent.futures.ThreadPoolExecutor(8) + + class ThreadPoolHandler(RequestHandler): + @gen.coroutine + def get(self): + for i in range(5): + print(i) + yield executor.submit(time.sleep, 1) + +See the :doc:`Asynchronous I/O ` chapter of the Tornado +user's guide for more on blocking and asynchronous functions. + + +My code is asynchronous, but it's not running in parallel in two browser tabs. +------------------------------------------------------------------------------ + +Even when a handler is asynchronous and non-blocking, it can be surprisingly +tricky to verify this. Browsers will recognize that you are trying to +load the same page in two different tabs and delay the second request +until the first has finished. To work around this and see that the server +is in fact working in parallel, do one of two things: + +* Add something to your urls to make them unique. Instead of + ``http://localhost:8888`` in both tabs, load + ``http://localhost:8888/?x=1`` in one and + ``http://localhost:8888/?x=2`` in the other. + +* Use two different browsers. For example, Firefox will be able to load + a url even while that same url is being loaded in a Chrome tab. diff --git a/docs/index.rst b/docs/index.rst index 609f3671..92dc1823 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -129,6 +129,7 @@ This documentation is also available in `PDF and Epub formats coroutine integration utilities + faq releases * :ref:`genindex`