Add an FAQ page.

This commit is contained in:
Ben Darnell 2015-03-04 00:01:05 -05:00
parent b06e7de206
commit 83a0691723
2 changed files with 97 additions and 0 deletions

96
docs/faq.rst Normal file
View File

@ -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 <https://github.com/tornadoweb/tornado/wiki/Links>`_
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 <https://github.com/tornadoweb/tornado/wiki/Links>`_
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 <guide/async>` 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.

View File

@ -129,6 +129,7 @@ This documentation is also available in `PDF and Epub formats
coroutine
integration
utilities
faq
releases
* :ref:`genindex`