异步调用的概念在很多与Web相关的编程中都是类似的,不仅仅适用于Tornado服务器。这里涉及的是一些“东西”(框架、服务器、库……)。
基本思想是:
- 在同步请求中,发送请求并停止执行程序,直到HTTP服务器响应请求(如果无法连接到服务器,则会出现错误;如果服务器太慢而不能回复,则会出现超时)。解释器在完成请求之前被阻塞(直到你得到有关请求的确定答案:是否成功?是否出错?是否超时?……)。
在异步请求中,你发起请求,然后就可以将其“遗忘”了,也就是说:请求发出后,解释器继续执行代码,而不必等待请求完成。
这似乎…没什么意义,对吧?你将请求发送到“太空虚无”中,并像平常一样继续执行吗?当服务器向你发送其响应时会发生什么?我发送了一个请求,我想知道它的结果!否则,我一开始就不会在我的代码中输入它!!
好吧,在这里,回调
就派上用场了。你将请求发给“太空虚无”,但是提供回调函数,因此当另一端的HTTP服务器向你发送其响应时,该函数将以该response
为第一个参数运行。
我们来看一个例子。
我创建了一个非常简单的Tornado服务器(使用Python 2.7
和Tornado 4.2
)只有一个处理程序。在GET
上,它需要5秒钟才能返回。我使用time.sleep实现这一点,在实际生活中,它可能是一个非常耗时的过程(访问数据库、执行一些计算……谁知道呢?…)
这是服务器文件(基于Tornado文档中提供的示例):
SampleServer.py
:
import time
import tornado.ioloop
import tornado.web
class MainHandler(tornado.web.RequestHandler):
def get(self):
print "Someone is GET'ing me"
time.sleep(5)
self.write("Hello, world")
application = tornado.web.Application([
(r"/", MainHandler),
])
if __name__ == "__main__":
application.listen(8888)
print "Starting sample server."
tornado.ioloop.IOLoop.current().start()
打开终端并运行该代码以启动服务器。您将在本地机器的端口8888
上获得一个Tornado。
现在,让我们创建另一个脚本(您需要在另一个终端中运行),以两种方式GET
http://localhost:8888
:首先同步,然后异步。
SampleFetcher.py
:
import datetime
import tornado.ioloop
from tornado.httpclient import HTTPClient, AsyncHTTPClient
def HUMAN_DT_NOW():
return datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S")
def synchronous_fetch(url):
http_client = HTTPClient()
start_dt = datetime.datetime.now()
response = http_client.fetch(url)
end_dt = datetime.datetime.now()
print ("The synchronous fetch took %s seconds."
% (end_dt - start_dt).total_seconds())
print "(Sync) Server said: \"%s\"" % response.body
def asynchronous_fetch(url):
http_client = AsyncHTTPClient()
def handle_response(response):
print ""
print "Yawwza... Finally!!!."
print "The time now is %s" % HUMAN_DT_NOW()
print "(Async) Server said: \"%s\"" % response.body
print "Gonna launch a 'fetch' to the universe at %s..." % HUMAN_DT_NOW()
http_client.fetch(url, callback=handle_response)
if __name__ == "__main__":
print " ------ Synchronous ------ "
print ("Starting synchronous fetch at %s."
" The program will block for about 5 secs." % HUMAN_DT_NOW())
synchronous_fetch('http://localhost:8888')
print "Pfew! That was a lot of wait time!!. I got bored watching my terminal"
print ""
print "Aight, let's see what Asynchronous can do"
print " ------ Asynchronous ------ "
asynchronous_fetch('http://localhost:8888')
print "You're gonna see this line before the \"Yawwza...\" one"
print "This one too. Now is %s" % HUMAN_DT_NOW()
tornado.ioloop.IOLoop.current().start()
这将输出:
Starting synchronous fetch at 2015/07/04 13:25:47. The program will block for about 5 secs.
The synchronous fetch took 5.009597 seconds.
(Sync) Server said: "Hello, world"
Pfew! That was a lot of wait time!!. I got bored watching my terminal
Aight, let's see what Asynchronous can do
------ Asynchronous ------
Gonna launch a 'fetch' to the universe at 2015/07/04 13:25:52...
You're gonna see this line before the "Yawwza..." one
This one too. Now is 2015/07/04 13:25:52
Yawwza... Finally!!!.
The time now is 2015/07/04 13:25:57
(Async) Server said: "Hello, world"
让我们集中关注这里的异步部分:
------ Asynchronous ------
Gonna launch a 'fetch' to the universe at 2015/07/04 13:25:52...
You're gonna see this line before the "Yawwza..." one
This one too. Now is 2015/07/04 13:25:52
Yawwza... Finally!!!.
The time now is 2015/07/04 13:25:57
(Async) Server said: "Hello, world"
如果你看到了,这个脚本在 13:25:52
做了 asynchronous_fetch
,但是立刻(在同一秒内),解释器继续执行,并且在请求被发出后运行下一个语句(打印You're gonna see this line before the "Yawwza..." one
和 This one too. Now is 2015/07/04 13:25:52
的那几行)。
然后,大约5秒钟后,服务器响应了,callback
函数(也就是handle_response
)被执行,这时你就看到了
Yawwza... Finally!!!.
The time now is 2015/07/04 13:25:57
希望这有助于对这个想法的理解。这是一个非常有用的概念,不仅适用于Tornado。
随意使用提供的两个示例脚本进行测试,更改内容,增加服务器响应的次数...
进一步推荐阅读: