【问题标题】:Why asyncio's run_in_executor blocks tornado's get handler?为什么 asyncio 的 run_in_executor 会阻塞 tornado 的 get 处理程序?
【发布时间】:2016-10-26 13:12:59
【问题描述】:

我想在 tornado 的异步 GET 请求处理程序中运行一个慢速阻塞方法(实际上来自第 3 方库)。让方法只是:

def blocking_method(uid):
    print("slow method started: ", uid)
    time.sleep(10)
    print("slow method done: ", uid)
    return "slow method ({}) result".format(uid)

此外,我更喜欢在 asyncio 的事件循环中运行 tornado 服务器:

if __name__ == '__main__':
    tornado.platform.asyncio.AsyncIOMainLoop().install()
    loop = asyncio.get_event_loop()
    loop.run_until_complete(make_app())
    loop.run_forever()

我知道 @run_in_executor 装饰器,但它不适合我,因为我使用 asyncio。要在异步协程中运行阻塞方法,我应该使用asyncio.get_event_loop()run_in_executor 方法。这是一个如何做到这一点的例子,来自this 答案:

import asyncio

async def main():
    loop = asyncio.get_event_loop()
    executor = concurrent.futures.ThreadPoolExecutor(max_workers=4)
    future1 = loop.run_in_executor(executor, blocking_method, 1)
    future2 = loop.run_in_executor(executor, blocking_method, 2)
    response1 = await future1
    response2 = await future2
    print(response1)
    print(response2)

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

而且效果很好,这是之前脚本的输出:

slow method started:  1
slow method started:  2
slow method done:  2
slow method done:  1
slow method (1) result
slow method (2) result

但如果我在 tornado 的 RequestHandler 的 async def get 方法中使用完全相同的技术:

class AsyncHandler(tornado.web.RequestHandler):

    async def get(self):
        #  simple counter to distinguish requests
        self.application.counter += 1
        in_msg = "Registered request #{}, working...".format(self.application.counter)
        print(in_msg)
        loop = asyncio.get_event_loop()
        future = loop.run_in_executor(self.application.executor,
                                      blocking_method,
                                      self.application.counter)
        result = await future
        out_msg = "Request processed, result: {}".format(result)
        print(out_msg)
        self.write(out_msg)

它阻塞了处理程序的方法。换句话说,如果我在多个浏览器选项卡中打开http://localhost:8888/(假设是两个),那么我期望两个请求并行工作,输出如下:

Registered request #1, working...
slow method started:  1
Registered request #2, working...
slow method started:  2
slow method done:  1
Request processed, result: slow method (1) result
slow method done:  2
Request processed, result: slow method (2) result

但是请求被执行结果

Registered request #1, working...
slow method started:  1
slow method done:  1
Request processed, result: slow method (1) result
Registered request #2, working...
slow method started:  2
slow method done:  2
Request processed, result: slow method (2) result

那么,我哪里错了?我应该怎么做才能允许并行执行请求处理程序?

这是描述我的问题的完整脚本:

import asyncio
import concurrent.futures
import time

import tornado.web
import tornado.platform


def blocking_method(uid):
    print("slow method started: ", uid)
    time.sleep(10)
    print("slow method done: ", uid)
    return "slow method ({}) result".format(uid)


class AsyncHandler(tornado.web.RequestHandler):

    async def get(self):
        #  simple counter to distinguish requests
        self.application.counter += 1
        in_msg = "Registered request #{}, working...".format(self.application.counter)
        print(in_msg)
        loop = asyncio.get_event_loop()
        future = loop.run_in_executor(self.application.executor,
                                      blocking_method,
                                      self.application.counter)
        result = await future
        out_msg = "Request processed, result: {}".format(result)
        print(out_msg)
        self.write(out_msg)

async def make_app():
    handlers = [(r"/", AsyncHandler)]
    app = tornado.web.Application(handlers, debug=True)
    app.executor = concurrent.futures.ThreadPoolExecutor(max_workers=4)
    app.counter = 0
    app.listen(8888)

if __name__ == '__main__':
    tornado.platform.asyncio.AsyncIOMainLoop().install()
    loop = asyncio.get_event_loop()
    loop.run_until_complete(make_app())
    loop.run_forever()

【问题讨论】:

  • run_in_executor 不会阻止,它是您的网络浏览器按顺序运行请求。看到这个similar question

标签: python tornado python-asyncio


【解决方案1】:

浏览器会识别出您正在尝试在两个不同的选项卡中加载同一页面,并将第二个请求延迟到第一个请求完成。

http://www.tornadoweb.org/en/latest/faq.html#why-isn-t-this-example-with-time-sleep-running-in-parallel

  • 在您的网址中添加一些内容以使其独一无二。不要在两个选项卡中使用http://localhost:8888,而是在一个选项卡中加载http://localhost:8888/?x=1,在另一个选项卡中加载http://localhost:8888/?x=2
  • 使用两种不同的浏览器。例如,即使在 Chrome 选项卡中加载相同的 url 时,Firefox 也可以加载该 url。

【讨论】:

    猜你喜欢
    • 2021-12-14
    • 1970-01-01
    • 2019-01-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-11
    • 2021-06-10
    • 1970-01-01
    相关资源
    最近更新 更多