Python3.5后 async/await(coroutine)使用问题总结

前言

在最近的项目中,需要实现一个周期性执行的定时器。第一个想法是用celery,但是看了一下之后发现针对这个简单的需求,celery还是太大了,有点杀鸡焉用牛刀的感觉。正好之前在看coroutine的东西,于是就在这方面试试。但是也遇到了好多问题吧,本文就是针对这些问题的总结,希望对大家有所帮助。

先放两个链接,是我学习coroutine时候看到的,感觉讲的挺好的,有必要后面翻译一下。
AsyncIO for the Working Python Developer
I don't understand Python's Asyncio

我都是用的Python3.5中添加的async/await来定义coroutine,所以本文适用于3.5和3.6。

Q1、coroutine的运行

根据官网的文档,有两个方法可以运行coroutine,run_foreverrun_until_complete

run_until_complete

import asyncio

async def hello():
    await asyncio.sleep(1)
    print('Hello')
    return 'World'

loop = asyncio.get_event_loop()
r = loop.run_until_complete(hello())
print(r)
loop.close()

# result
Hello
World

run_until_complete等待hello运行完,返回hello的结果。

run_forever

import asyncio

async def hello(future):
    await asyncio.sleep(1)
    print('Hello')
    future.set_result('World')


def callback(future):
    print(future.result())
    loop.stop()

loop = asyncio.get_event_loop()
ft = asyncio.Future()
asyncio.ensure_future(hello(ft))
ft.add_done_callback(callback)
try:
    loop.run_forever()
finally:
    loop.close()

# result
Hello
World

如果使用run_forever,那么coroutine的运行需要asyncio.ensure_future。这时候可以创建一个Future用于保存hello的运行结果。
add_done_callbackFuture添加hello方法的回调。

在回调里有loop.stop(),用于停止loop.run_forever()

Q2、 在同步函数中执行coroutine

我在开发过程中遇到这样一个问题,必须要将coroutine封在一个同步函数内,同时还要能拿到这个coroutine的执行结果。苦恼了半天没什么好方法,我又仔细地拜读了下官方文档,发现await后面跟着的不仅可以是coroutine,还可以是Future以及所有awaitable的对象。

Awaitable: An object that can be used in an await
expression. Can be a coroutine or an object with an __await__()
method. See also PEP 492.

import asyncio


async def async_hello(future):
    print('async hello')
    await asyncio.sleep(1)
    future.set_result('Hello World')


def hello():
    ft = asyncio.Future()
    asyncio.ensure_future(async_hello(ft))
    # 重点就是这里,返回一个Future
    return ft


async def run():
    print('Run ...')
    r = await hello()
    print(r)


loop = asyncio.get_event_loop()
loop.run_until_complete(run())
loop.close()

Q3、 开启Debug模式

官网文档
官网文档中讲的很清楚了,也比较简单,我就不赘述了。

Q4、Task exception was never retrieved

刚开始的时候,我总是遇到这个异常,很是头大,最终也还是在官网文档找到了解决方法。
我这里提供一个最终使用的方案

import asyncio

async def task():
    """最终执行的函数"""
    while True:
        print('something')
        await asyncio.sleep(1)
 
async def run(lp):
    """将task封装了一下,提供cancel和其他异常的处理"""
    try:
        await task()
    except asyncio.CancelledError:
        print('Cancel the future.')
    except Exception as e:
        print(e)
        # 引发其他异常后,停止loop循环
        lp.stop()

loop = asyncio.get_event_loop()
future = asyncio.ensure_future(run(loop))
try:
    loop.run_forever()
except KeyboardInterrupt:
    future.cancel()
    loop.run_until_complete(future)
finally:
    # 不管是什么异常,最终都要close掉loop循环
    loop.close()

到这里,基础的coroutine执行已经差不多了,后面则是第三方库的使用总结。

Q4、aiomysql

import asyncio

import aiomysql
from aiomysql.cursors import DictCursor


async def mysql_test():
    pool = await aiomysql.create_pool(
        host='localhost', port=3306, user='root',
        password='', db='mysql'
    )
    async with pool.acquire() as conn:
        await conn.set_charset('utf8')
        async with conn.cursor(DictCursor) as cur:
            await cur.execute('SELECT Host,User FROM user')
            print(cur.rowcount)
            print(await cur.fetchone())
            print(await cur.fetchall())
        # 如果是插入或者修改则需要commit
        # await conn.commit()

loop = asyncio.get_event_loop()
loop.run_until_complete(mysql_test())
loop.close()

如果你不想每个地方都要重新定义pool和conn,那么你可以这样来:

import asyncio

import aiomysql
from aiomysql.cursors import DictCursor


class MysqlWrapper:
    def __init__(self, host, port, user, password, db):
        self.host = host
        self.port = port
        self.user = user
        self.password = password
        self.db = db
        self._pool = None

    async def pool(self):
        if not self._pool:
            self._pool = await aiomysql.create_pool(
                host=self.host, port=self.port, user=self.user,
                password=self.password, db=self.db
            )
        return self._pool

    async def close(self):
        if not self._pool:
            return
        self._pool.close()
        await self._pool.wait_closed()

mysql = MysqlWrapper(
    host='localhost', port=3306, user='root',
    password='', db='mysql'
)


def mysql_context(wrapper):
    def _mysql_context(func):
        async def __mysql_context(*args, **kwargs):
            pool = await wrapper.pool()
            async with pool.acquire() as conn:
                await conn.set_charset('utf8')
                r = await func(conn=conn, *args, **kwargs)
                await conn.commit()
            return r
        return __mysql_context
    return _mysql_context


@mysql_context(mysql)
async def mysql_test(conn=None):
    async with conn.cursor(DictCursor) as cur:
        await cur.execute('SELECT Host,User FROM user')
        print(cur.rowcount)
        print(await cur.fetchone())
        print(await cur.fetchall())


async def close_pool():
    await mysql.close()
    print('Close mysql pool')


loop = asyncio.get_event_loop()
loop.run_until_complete(mysql_test())
loop.run_until_complete(close_pool())
loop.close()

Q5、aioredis

import asyncio

import aioredis


async def redis_test():
    pool = await aioredis.create_pool(('localhost', 6379), db=0)
    async with pool.get() as conn:
        print(await conn.keys('*'))
    pool.close()
    await pool.wait_closed()


loop = asyncio.get_event_loop()
loop.run_until_complete(redis_test())
loop.close()

Q6、aiohttp

import asyncio

import aiohttp


async def http_test():
    async with aiohttp.ClientSession() as session:
        async with session.get('http://www.cip.cc/') as resp:
            print(await resp.text())

loop = asyncio.get_event_loop()
loop.run_until_complete(http_test())
loop.close()

aioredis和aiohttp同样有封装,请看gist

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容