Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

在tornado中使用会不会出现你说的与asyncio自有协程不兼容的问题? #1

Closed
HunDunDM opened this issue May 5, 2017 · 6 comments
Labels

Comments

@HunDunDM
Copy link

HunDunDM commented May 5, 2017

七牛官方没有提供async的SDK,在pip上找到的。学习一个。

观察到使用了aiohttp,在你的另一篇文章中提到,由于使用了asyncio特有的协程,会出现一定的问题。「我自己在python3.6 + tornado4.5.1下复现了该问题」因此,该SDK是否不能直接在tornado中使用?

ps. 此前我在处理run_on_executor的时候碰到过类似的问题,run_on_executor的协程不能直接await,需要使用asyncio.futures.wrap_future装饰一层。与你的文章提到的用装饰器解决方案很类似。

@JZQT
Copy link
Owner

JZQT commented May 8, 2017

这个aioqiniu是使用标准协程的asyncawait关键字构建的。

如果你想在tornado中使用它那么tornado的ioloop应该设置成兼容asyncio的。

那个aiohttptornado中的使用问题我博客里面的一篇文章里面提到了。这个问题是tornado目前暂时无法很好地兼容asyncio所致。

我的文档里面提到了个暂时的解决方案就是将协程函数返回的协程转换为Future或者Task,现在看来还是转换成Future更加干净。

tornado已经支持完全使用asyncio的Future以及标准协程了(asyncawait关键字,yield from这个我没试过)。我给你一个样例

#!/usr/bin/env python3
# coding: utf-8

import asyncio
import functools

import aioqiniu
import tornado.web
from tornado.platform.asyncio import AsyncIOMainLoop

QINIU_ACCESS_KEY = "QINIU_ACCESS_KEY"
QINIU_SECRET_KEY = "QINIU_SECRET_KEY"


def convert_asyncio_future(method):
    """解决tornado中使用aiohttp的ClientSession的不兼容问题"""
    @functools.wraps(method)
    async def wrapper(self, *args, **kwargs):
        coro = method(self, *args, **kwargs)
        return await asyncio.ensure_future(coro)
    return wrapper


class QueryFileStatHandler(tornado.web.RequestHandler):
    """查询七牛云文件对象数据的请求处理器"""

    def initialize(self, qiniuclient):
        self.qiniuclient = qiniuclient

    @convert_asyncio_future
    async def get(self, bucket: str, key: str):
        res = await self.qiniuclient.get_file_stat(bucket, key)
        self.write(res)

    pass


async def create_qiniu_client(*args, **kwargs):
    """在协程中创建七牛客户端

    避免七牛客户端自动创建aiohttp的ClientSession因不在协程中创建而发出提示信息。
    """
    return aioqiniu.QiniuClient(*args, **kwargs)


def main():
    # tornado使用asyncio的主IO循环,即`asyncio.get_event_loop()`
    AsyncIOMainLoop().install()
    loop = asyncio.get_event_loop()

    qiniuclient = loop.run_until_complete(
        create_qiniu_client(QINIU_ACCESS_KEY, QINIU_SECRET_KEY))
    params = {"qiniuclient": qiniuclient}

    app = tornado.web.Application([
        (r"/query/(.*?)/(.*?)", QueryFileStatHandler, params),
    ])
    app.listen(8888)

    loop.run_forever()


if __name__ == "__main__":
    main()

我在Linux下试验这个代码是没有问题的。

@JZQT JZQT added the question label May 8, 2017
@JZQT
Copy link
Owner

JZQT commented May 8, 2017

忘了说我的Python版本是3.5,tornado版本是4.4.2

你在你那边自己测一测看看吧。

另外,run_on_executor是使用线程,现在用了异步的客户端就可以不用使用线程了吧。

@HunDunDM
Copy link
Author

HunDunDM commented May 9, 2017

非常感谢,之前没注意ClientSession的问题,也能一并解决掉了。在我的环境下测试了是没问题的,windows下我也测了一遍~
之前有稍微看了一下tornadoasyncio的源码,推测yield from也是没有问题的,语义上基本是等价await。不过3.5+已经基本用不到了,也就不必深究了。

Ps. 用了你的SDK后,七牛这部分的run_on_executor确实是都拿掉了。不过还是有一部分是拿不掉的,是计算量大的几个函数,如果不用run_on_executor会阻塞工作线程。

Ps2. 看源码在本场景下ensure_future本质应该还是create_task,不过这个函数确实适用场景更广。

Ps3. 装饰器定义成这样,可能有些微小的性能提升。

def convert_asyncio_future(method):
    """解决tornado中使用aiohttp的ClientSession的不兼容问题"""
    @functools.wraps(method)
    def wrapper(*args, **kwargs):
        return asyncio.ensure_future(method(*args, **kwargs))
    return wrapper

@JZQT
Copy link
Owner

JZQT commented May 9, 2017

  1. Python的全局解释锁导致Python不能支持多线程并行计算,如果你的那些函数是计算密集型尤其是纯计算函数,那么使用run_on_executor是没多少帮助的,可能还增加了线程切换上下文的开销,如果是IO密集型的任务,用run_on_executor是有提升的。

  2. 你说的没错,我仅仅是认为它在语义和概念上更符合这个场景。

  3. 是的,更重要的是少写了一行代码......

@HunDunDM
Copy link
Author

HunDunDM commented May 9, 2017

  1. 计算性能确实是下降的,只是为了防止计算函数卡住响应。

  2. 返回awaitable对象的普通函数,还是可以被await调用的,这样在实际运行的时候会少生成一个协程对象。虽然好像也没什么意义-_-|||

Ps. 使用时发现图片处理场景需要访问_成员,提了一个pull request

@JZQT
Copy link
Owner

JZQT commented May 9, 2017

好的,等我整理之后准备下一版本

@JZQT JZQT closed this as completed May 9, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants