【问题标题】:Twisted Klein: Synchronous behaviorTwisted Klein:同步行为
【发布时间】:2023-12-26 12:33:02
【问题描述】:

我使用 Twisted Klein 是因为框架的一个承诺是它是异步的,但我测试了我开发的应用程序和一些测试代码,框架行为似乎是同步的。

测试服务器代码为:

# -*- encoding: utf-8 -*-
import json
import time
from datetime import datetime

from klein import Klein

app = Klein()

def setHeader(request, content_type):

    request.setHeader('Access-Control-Allow-Origin', '*')
    request.setHeader('Access-Control-Allow-Methods', 'GET')
    request.setHeader('Access-Control-Allow-Headers', 'x-prototype-version,x-requested-with')
    request.setHeader('Access-Control-Max-Age', 2520)
    request.setHeader('Content-type', content_type)


def cleanParams(params):

    for key in params.keys():

        param = params[key]
        params[key] = param[0]

    return params


@app.route('/test/', methods=["GET"])
def test(request):

    setHeader(request,'application/json')

    time.sleep(5)

    return json.dumps([str(datetime.now())])

if __name__ == "__main__":
    app.run(host='0.0.0.0',port=12030)

而测试请求是:

# -*- encoding: utf-8 -*-
import requests
from datetime import datetime

if __name__ == "__main__":

    url = "http://192.168.50.205:12030"

    params = {
    }

    print datetime.now()
    for i in xrange(6):
        result = requests.get(url + "/test/", params)

        print datetime.now(), result.json()

服务器启动后,如果我单独运行第二个代码:

2016-07-19 12:50:53.530000
2016-07-19 12:50:58.570000 [u'2016-07-19 12:50:58.548000']
2016-07-19 12:51:03.604000 [u'2016-07-19 12:51:03.589000']
2016-07-19 12:51:08.634000 [u'2016-07-19 12:51:08.625000']
2016-07-19 12:51:13.670000 [u'2016-07-19 12:51:13.654000']
2016-07-19 12:51:18.717000 [u'2016-07-19 12:51:18.708000']
2016-07-19 12:51:23.764000 [u'2016-07-19 12:51:23.748000']

完美,但如果我同时运行两个实例:

实例 1:

2016-07-19 12:53:05.025000
2016-07-19 12:53:10.057000 [u'2016-07-19 12:53:10.042000']
2016-07-19 12:53:20.113000 [u'2016-07-19 12:53:20.097000']
2016-07-19 12:53:30.181000 [u'2016-07-19 12:53:30.166000']
2016-07-19 12:53:40.236000 [u'2016-07-19 12:53:40.219000']
2016-07-19 12:53:50.316000 [u'2016-07-19 12:53:50.294000']
2016-07-19 12:54:00.381000 [u'2016-07-19 12:54:00.366000']

实例 2:

2016-07-19 12:53:05.282000
2016-07-19 12:53:15.074000 [u'2016-07-19 12:53:15.059000']
2016-07-19 12:53:25.141000 [u'2016-07-19 12:53:25.125000']
2016-07-19 12:53:35.214000 [u'2016-07-19 12:53:35.210000']
2016-07-19 12:53:45.270000 [u'2016-07-19 12:53:45.255000']
2016-07-19 12:53:55.362000 [u'2016-07-19 12:53:55.346000']
2016-07-19 12:54:05.402000 [u'2016-07-19 12:54:05.387000']

还有服务器输出:

2016-07-19 12:53:10-0400 [-] "192.168.50.205" - - [19/Jul/2016:16:53:04 +0000] "GET /test/ HTTP/1.1" 200 30 "-" "python-requests/2.9.1"
2016-07-19 12:53:15-0400 [-] "192.168.50.205" - - [19/Jul/2016:16:53:10 +0000] "GET /test/ HTTP/1.1" 200 30 "-" "python-requests/2.9.1"
2016-07-19 12:53:20-0400 [-] "192.168.50.205" - - [19/Jul/2016:16:53:15 +0000] "GET /test/ HTTP/1.1" 200 30 "-" "python-requests/2.9.1"
2016-07-19 12:53:25-0400 [-] "192.168.50.205" - - [19/Jul/2016:16:53:20 +0000] "GET /test/ HTTP/1.1" 200 30 "-" "python-requests/2.9.1"
2016-07-19 12:53:30-0400 [-] "192.168.50.205" - - [19/Jul/2016:16:53:25 +0000] "GET /test/ HTTP/1.1" 200 30 "-" "python-requests/2.9.1"
2016-07-19 12:53:35-0400 [-] "192.168.50.205" - - [19/Jul/2016:16:53:30 +0000] "GET /test/ HTTP/1.1" 200 30 "-" "python-requests/2.9.1"
2016-07-19 12:53:40-0400 [-] "192.168.50.205" - - [19/Jul/2016:16:53:35 +0000] "GET /test/ HTTP/1.1" 200 30 "-" "python-requests/2.9.1"
2016-07-19 12:53:45-0400 [-] "192.168.50.205" - - [19/Jul/2016:16:53:40 +0000] "GET /test/ HTTP/1.1" 200 30 "-" "python-requests/2.9.1"
2016-07-19 12:53:50-0400 [-] "192.168.50.205" - - [19/Jul/2016:16:53:45 +0000] "GET /test/ HTTP/1.1" 200 30 "-" "python-requests/2.9.1"
2016-07-19 12:53:55-0400 [-] "192.168.50.205" - - [19/Jul/2016:16:53:50 +0000] "GET /test/ HTTP/1.1" 200 30 "-" "python-requests/2.9.1"
2016-07-19 12:54:00-0400 [-] "192.168.50.205" - - [19/Jul/2016:16:53:55 +0000] "GET /test/ HTTP/1.1" 200 30 "-" "python-requests/2.9.1"
2016-07-19 12:54:05-0400 [-] "192.168.50.205" - - [19/Jul/2016:16:54:00 +0000] "GET /test/ HTTP/1.1" 200 30 "-" "python-requests/2.9.1"

如您所见,服务器正在阻止当前的执行,并且它似乎在同步而不是异步地工作。

我错过了什么?

最好的问候。

【问题讨论】:

    标签: python asynchronous twisted synchronous klein-mvc


    【解决方案1】:

    您遗漏了 Twisted 的许多重要概念。关于同步行为,您是绝对正确的,如果您没有明确使用异步函数(即Deferreds),Klein 的行为就像同步框架,如 Flask 或 Bottle。在您的示例中,您没有使用任何异步功能,因此您的代码按顺序执行。查看https://github.com/notoriousno/klein-basics/blob/intro/nonblocking.rst 这应该可以帮助您了解 Klein 和 Twisted 中的异步基础知识。提醒读者延迟不会使您的代码神奇地异步!您必须仔细设计以实现并发执行。

    使您的代码异步

    让我们尝试修复您的代码,使其异步运行。我将分节讨论这些概念。如果需要更多信息,请发表评论,我会解决它。让我们从所需的导入开始:

    from klein import Klein
    from twisted.internet import defer, reactor
    

    setHeader()

    接下来让我们看看更改setHeader() 函数。 request.setHeader 函数相当快,因此可以多次运行而不会出现严重阻塞。因此,可以使用生成带有回调的Deferred 对象的函数,该回调将设置各种标头键/值对:

    def setHeader(request, content_type):
    
        def _setHeader(previous_result, header, value):
            request.setHeader(header, value)
    
        d = defer.Deferred()
        d.addCallback(_setHeader, 'Access-Control-Allow-Origin', '*')
        d.addCallback(_setHeader, 'Access-Control-Allow-Methods', 'GET')
        d.addCallback(_setHeader, 'Access-Control-Allow-Headers', 'x-prototype-version,x-requested-with')
        d.addCallback(_setHeader, 'Access-Control-Max-Age', '2520')
        d.addCallback(_setHeader, 'Content-type', content_type)
        return d
    

    没有详细说明,我们使用Deferred.addCallback() 将回调链接在一起。在这种情况下,回调函数是本地的_setHeader(),它只是简单地设置了头部。最后,该函数将返回Deferred。如果你注意到了,_setHeader() 接受了一个参数previous_result,让我们暂时忽略它们。

    cleanParams()

    如果正在使用循环(forwhile),通常最好使用inlineCallbacksyield 结果。使用此方法可以让您以同步方式运行,而不会阻塞主 ioloop。

    @defer.inlineCallbacks
    def cleanParams(params):
        for key in sorted(params):
            param = params[key]
            params[key] = yield param[0]
    
        defer.returnValue(str(params))    # if py3 then use ``return params``
    

    这是一个不好的例子,但它应该说明如何使用yield 来等待一个值。附带说明一下,setHeader() 函数也可以使用inlineCallbacksyields。我想演示多种异步样式。

    克莱因路线

    最后,让我们在路由中实际使用异步函数:

    app = Klein()
    
    @app.route('/test/', methods=["GET"])
    def test(request):
        asyncClean = cleanParams(request.args)
        asyncClean.addCallback(request.write)
    
        asyncSetHeader = setHeader(request,'application/json')
        reactor.callLater(5, asyncSetHeader.callback, None)
    
        def render(results, req):
            req.write(json.dumps([str(datetime.now())]))
    
        finalResults = defer.gatherResults([asyncClean, asyncSetHeader])
        finalResults.addCallback(render, request)
        return finalResults
    

    不要惊慌!首先我们调用cleanParams(),它返回一个Deferred,当它完成时,returnValue 将被写入响应正文。接下来,标题将通过我们的setHeader() 设置,它显式返回Deferred。您正在使用time.sleep(5),而您无意中阻塞了整个反应器回路。在 Klein/Twisted 中,如果您想稍后再做某事,您通常会使用 callLater()。最后,我们通过gatherResults() 等待延迟asyncCleanasyncSetHeader 的结果,并将时间戳写入响应正文。

    最终代码

    server.py

    import json
    from datetime import datetime
    
    from klein import Klein
    from twisted.internet import defer, reactor
    
    
    app = Klein()
    
    def setHeader(request, content_type):
    
        def _setHeader(previous_result, header, value):
            request.setHeader(header, value)
    
        d = defer.Deferred()
        d.addCallback(_setHeader, 'Access-Control-Allow-Origin', '*')
        d.addCallback(_setHeader, 'Access-Control-Allow-Methods', 'GET')
        d.addCallback(_setHeader, 'Access-Control-Allow-Headers', 'x-prototype-version,x-requested-with')
        d.addCallback(_setHeader, 'Access-Control-Max-Age', '2520')
        d.addCallback(_setHeader, 'Content-type', content_type)
        return d
    
    
    @defer.inlineCallbacks
    def cleanParams(params):
        for key in sorted(params):
            param = params[key]
            params[key] = yield param[0]
    
        defer.returnValue(str(params))
    
    
    @app.route('/test/', methods=["GET"])
    def test(request):
        asyncClean = cleanParams(request.args)
        asyncClean.addCallback(request.write)       # write the result from cleanParams() to the response
    
        asyncSetHeader = setHeader(request,'application/json')
        reactor.callLater(5, asyncSetHeader.callback, None)
    
        def render(results, req):
            req.write(json.dumps([str(datetime.now())]))
    
        finalResults = defer.gatherResults([asyncClean, asyncSetHeader])
        finalResults.addCallback(render, request)
        return finalResults
    
    if __name__ == "__main__":
        app.run(host='0.0.0.0',port=12030)
    

    test.sh

    curl -v -X GET http://localhost:12030/test/?hello=world\&foo=bar\&fizz=buzz
    

    【讨论】:

    • 感谢您的回答。我将查看链接以了解如何使用它。
    • @DarioGuajardo 实际上回顾你的问题,你在异步编程的许多方面都非常错误。我下班时会提供更深入的答案。请记住,Klein 或 Twisted 框架不会让您的代码“神奇地”异步。
    • 与 gevent+flask 异步代码比较 - gist.github.com/viksit/b6733fe1afdf5bb84a40
    最近更新 更多