I've written asynchronous programs using Tornado and asyncio, however I've realized I don't understand how asynchronous tasks say they're done.
For example, let's take a look at the asynchronous fetch in http://www.tornadoweb.org/en/stable/guide/async.html#examples.
My understanding thus far is:
- The handler is suspended when
fetchyields aFuture. - The
Futureis added to theIOLoopvia http://www.tornadoweb.org/en/stable/ioloop.html#tornado.ioloop.IOLoop.add_future - The
Futurefinishes, and theIOLoopschedules the coroutine to be reanimated so it can complete.
What I don't understand is how the Future in step 3 "finishes" and invokes its done callback. I thought there was only one thread, so how would the Future "work in the background" and get control so it could invoke the callback?
The IOLoop opens a socket to the remote server you're fetching from, and adds that socket to a list of file descriptors on which it's waiting for IO using epoll or a similar system call.
Whenever the loop is not executing your code -- for example, when your handler is paused by
yield, the loop is waiting for IO, here:https://github.com/tornadoweb/tornado/blob/master/tornado/ioloop.py#L862
When it receives an IO event -- for example, when the remote server sends some response bytes -- Tornado finds the callback that was waiting for that event and executes it.
For an example implementation of an event loop, see A Web Crawler With asyncio Coroutines.