asyncio简明教程

1. 前言

asyncio 是用来编写并发代码的库,使用 async/await 语法。

asyncio 被用作多个提供高性能 Python 异步框架的基础,包括网络和网站服务,数据库连接库,分布式任务队列等等。asyncio 往往是构建 IO 密集型的最佳选择。

asyncio 是Python 3.4版本引入的标准库,直接内置了对异步IO的支持。如果你在 Python 早期版本中,想使用类似的功能,可以了解一下 gevent 库和 twisted 库。

2. asyncio定义

asyncio 模块提供了一个围绕事件循环的框架。事件循环就是等待事件发生,然后对事件进行操作,依赖这种方式实现异步IO。

  • event_loop:时间循环,开启之后,可以将协程注册进来。
  • task:一个协程对象就是一个可以挂起的函数,任务是对协程的进一步封装,其中包含了任务的各种状
  • future:代表将来执行或没有执行的任务的结果。task可以说是future的子类。

下面以一段代码为例子

import asyncio

@asyncio.coroutine
def hello1():
    print("request https://www.xtuz.net 1")
    # 异步调用asyncio.sleep(1):
    yield from asyncio.sleep(3)
    print("response https://www.xtuz.net 1")

@asyncio.coroutine
def hello2():
    print("request https://www.xtuz.net 2")
    # 异步调用asyncio.sleep(1):
    yield from asyncio.sleep(2)
    print("response https://www.xtuz.net 2")



# 获取EventLoop:
loop = asyncio.get_event_loop()
# 执行coroutine
tasks = [hello1(), hello2()]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()

在asyncio库中,协程使用 @asyncio.coroutine 装饰来定义一个Task,并将放入到 event_loop 的循环中,使用 yield from 来驱动。

上述代码输出如下:

request https://www.xtuz.net 1
request https://www.xtuz.net 2
response https://www.xtuz.net 2
response https://www.xtuz.net 1

在python3.5中作了如下更改

@asyncio.coroutine => async

yield from => await

基于此,我们可以将上述代码进行改写,代码看起来要逻辑清晰很多。

import asyncio

async def hello1():
    print("request https://www.xtuz.net 1")
    # 异步调用asyncio.sleep(1):
    await asyncio.sleep(3)
    print("response https://www.xtuz.net 1")

async def hello2():
    print("request https://www.xtuz.net 2")
    # 异步调用asyncio.sleep(1):
    await asyncio.sleep(2)
    print("response https://www.xtuz.net 2")

# 获取EventLoop:
loop = asyncio.get_event_loop()
# 执行coroutine
tasks = [hello1(), hello2()]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()

2.1 创建Task

loop.create_task()

接收一个协程,返回一个 asyncio.Task 的实例,也是 asyncio.Future 的实例。

返回值可直接传入 run_until_complete()

2.2 获取Task返回值

task.result()

可通过调用 task.result() 方法来获取协程的返回值,但是只有运行完毕后才能获取,若没有运行完毕,result()方法不会阻塞去等待结果,而是抛出 asyncio.InvalidStateError 错误。

2.3 控制Task

asyncio.wait()

asyncio.wait() 是一个协程,不会阻塞,立即返回,返回的是协程对象。传入的参数是future或协程构成的可迭代对象。最后将返回值传给run_until_complete()加入事件循环。

2.4 动态添加Task

  • *loop.call_soon_threadsafe() * 等待此函数返回后马上调用回调函数,返回值是一个 asyncio.Handle 对象,此对象内只有一个方法为 cancel()方法,用来取消回调函数。
  • *loop.call_soon() * 与call_soon_threadsafe()类似,call_soon_threadsafe() 是线程安全
  • loop.call_later() 延迟多少秒后执行回调函数
  • loop.call_at() 在指定时间执行回调函数,这里的时间统一使用 loop.time() 来替代 time.sleep()
  • asyncio.run_coroutine_threadsafe() 动态的加入协程,参数为一个回调函数和一个loop对象,返回值为future对象,通过future.result()获取回调函数返回值

3. asyncio启动一个协程

asyncio事件循环可以通过多种不同的方法启动一个协程。一般对于入口函数,最简答的方法就是使用run_until_complete(),并将协程直接传入这个方法。

import asyncio

async def foo():
    print("https://www.xtuz.net")

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

4. asyncio协程获取返回值

run_until_complete 可以获取协程的返回值,如果没有给定返回值,则像函数一样,默认返回None

import asyncio

async def foo():
    print("https://www.xtuz.net")
    return 'foo'

loop = asyncio.get_event_loop()
ret = loop.run_until_complete(foo())
print(ret)
loop.close()

5. asyncio中协程相互调用

一个协程可以启动另一个协程,从而可以任务根据工作内容,封装到不同的协程中。我们可以在协程中使用await关键字,链式的调度协程,来形成一个协程任务流。

import asyncio

async def foo2(url):
    print(url)
    return 'foo2'

async def foo():
    print("https://www.xtuz.net")
    ret = await foo2('https://www.cmypsc.com')
    return ret

loop = asyncio.get_event_loop()
ret = loop.run_until_complete(foo())
print(ret)
loop.close()

6. 小结

asyncio提供了完善的异步IO支持;

异步操作需要在coroutine中通过await完成;

多个coroutine可以封装成一组Task然后并发执行。

参考阅读

展开剩余53%