同步与异步

14

我正在尝试理解Tornado文档介绍页面提供的基本示例。它有两个代码块。同步的那一个对我来说很好,我也理解了它。但是异步的那个我无法理解。

同步

from tornado.httpclient import HTTPClient

def synchronous_fetch(url):
    http_client = HTTPClient()
    response = http_client.fetch(url)
    return response.body

异步的

from tornado.httpclient import AsyncHTTPClient

def asynchronous_fetch(url, callback):
    http_client = AsyncHTTPClient()
    def handle_response(response):
        callback(response.body)
    http_client.fetch(url, callback=handle_response)

如果您可以提供一个更好的例子,请尽管提供。

1个回答

53

异步调用的概念在很多与Web相关的编程中都是类似的,不仅仅适用于Tornado服务器。这里涉及的是一些“东西”(框架、服务器、库……)。

基本思想是:

  • 在同步请求中,发送请求并停止执行程序,直到HTTP服务器响应请求(如果无法连接到服务器,则会出现错误;如果服务器太慢而不能回复,则会出现超时)。解释器在完成请求之前被阻塞(直到你得到有关请求的确定答案:是否成功?是否出错?是否超时?……)。
  • 在异步请求中,你发起请求,然后就可以将其“遗忘”了,也就是说:请求发出后,解释器继续执行代码,而不必等待请求完成。

    这似乎…没什么意义,对吧?你将请求发送到“太空虚无”中,并像平常一样继续执行吗?当服务器向你发送其响应时会发生什么?我发送了一个请求,我想知道它的结果!否则,我一开始就不会在我的代码中输入它!!

    好吧,在这里,回调就派上用场了。你将请求发给“太空虚无”,但是提供回调函数,因此当另一端的HTTP服务器向你发送其响应时,该函数将以该response为第一个参数运行。

我们来看一个例子。

我创建了一个非常简单的Tornado服务器(使用Python 2.7Tornado 4.2)只有一个处理程序。在GET上,它需要5秒钟才能返回。我使用time.sleep实现这一点,在实际生活中,它可能是一个非常耗时的过程(访问数据库、执行一些计算……谁知道呢?…)

这是服务器文件(基于Tornado文档中提供的示例):

SampleServer.py:

#!/usr/bin/env python2.7
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

#!/usr/bin/env python2.7
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()
    # The IOLoop below is required to prevent the script from closing ahead
    # of time, but allowing Asynchronous interactions
    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..." oneThis 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。

随意使用提供的两个示例脚本进行测试,更改内容,增加服务器响应的次数...

进一步推荐阅读:


2
好的帖子和示例。谢谢。 - Spouk
1
讲解得非常清楚,谢谢。 - phraniiac
很高兴我能帮到你 :-) 祝你编程愉快!! - Savir

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接