asyncio模块

asyncio模块

asyncio 可以用来搞异步网络请求,并发,协程。

协程通过 async/await 语法进行声明,是编写异步应用的推荐方式。

python3.0时代,标准库里的异步网络模块:select(非常底层) python3.0时代,第三方异步网络库:Tornado python3.4时代,asyncio:支持TCP,子进程

现在的asyncio,有了很多的模块已经在支持:aiohttp,aiodns,aioredis等等 https://github.com/aio-libs 这里列出了已经支持的内容,并在持续更新

当然到目前为止实现协程的不仅仅只有asyncio,tornado和gevent都实现了类似功能

关键字

  1. async/await , async用来定义一个协程,await用于挂起阻塞的异步调用接口
  2. event_loop ,事件循环,程序开启一个无限循环,把一些函数注册到事件循环上,当满足事件发生的时候,调用相应的协程函数
  3. coroutine 协程,协程对象,指一个使用async关键字定义的函数,它的调用不会立即执行函数,而是会返回一个协程对象。协程对象需要注册到事件循环,由事件循环调用
  4. task 任务,一个协程对象就是一个原生可以挂起的函数,任务则是对协程进一步封装,其中包含了任务的各种状态
  5. future ,代表将来执行或没有执行的任务的结果。它和task上没有本质上的区别

定义一个协程

demo

import time
import asyncio

now = lambda: time.time()


async def do_some_work(x):
    print("waiting:", x)


start = now()
# 这里是一个协程对象,这个时候do_some_work函数并没有执行
coroutine = do_some_work(2)
print(coroutine)
#  创建一个事件loop
loop = asyncio.get_event_loop()
# 将协程加入到事件循环loop
loop.run_until_complete(coroutine)

print("Time:", now() - start)

多个协程 demo1

import asyncio


async def func(i):
    print('start {}th'.format(i))
    await asyncio.sleep(1)
    print('finish {}th'.format(i))


loop = asyncio.get_event_loop()
coroutine_list = (func(i) for i in range(10))
loop.run_until_complete(asyncio.gather(*coroutine_list))

多个协程 demo2

import asyncio


async def func(i):
    print('start {}th'.format(i))
    await asyncio.sleep(1)
    print('finish {}th'.format(i))


loop = asyncio.get_event_loop()
task_list = [asyncio.ensure_future(func(i)) for i in range(10)]
loop.run_until_complete(asyncio.wait(task_list))
# loop.run_until_complete(asyncio.gather(*task_list))  同样效果

创建一个task

协程对象不能直接运行,在注册事件循环的时候,其实是run_until_complete方法将协程包装成为了一个任务(task)对象. task对象是Future类的子类,保存了协程运行后的状态,用于未来获取协程的结果

import asyncio
import time

now = lambda: time.time()


async def do_some_work(x):
    print("waiting:", x)


start = now()

coroutine = do_some_work(2)
loop = asyncio.get_event_loop()
task = loop.create_task(coroutine)  # 3.7新功能
# task = asyncio.ensure_future(coroutine) # 没有loop情况下创建 task
print(task)
loop.run_until_complete(task)
print(task)
print("Time:", now() - start)

绑定回调

绑定回调,在task执行完成的时候可以获取执行的结果,回调的最后一个参数是future对象,通过该对象可以获取协程返回值。

通过add_done_callback方法给task任务添加回调函数,当task(也可以说是coroutine)执行完成的时候,就会调用回调函数。并通过参数future获取协程执行的结果。这里我们创建 的task和回调里的future对象实际上是同一个对象

import time
import asyncio

now = lambda: time.time()


async def do_some_work(x):
    print("waiting:", x)
    return "Done after {}s".format(x)


def callback(future):
    print("callback:", future.result())


start = now()
loop = asyncio.get_event_loop()
task = asyncio.ensure_future(do_some_work(2))
print(task)
task.add_done_callback(callback)
print(task)
loop.run_until_complete(task)

print("Time:", now() - start)

阻塞和await

使用async可以定义协程对象,使用await可以针对耗时的操作进行挂起,就像生成器里的yield一样,函数让出控制权。协程遇到await,事件循环将会挂起该协程,执行别的协程,直到其他的协程也挂起或者执行完毕,再进行下一个协程的执行

耗时的操作一般是一些IO操作,例如网络请求,文件读取等。我们使用asyncio.sleep函数来模拟IO操作。协程的目的也是让这些IO操作异步化。

import asyncio
import time
import random

now = lambda: time.time()


async def do_some_work(x):
    print("waiting:", x)
    # await 后面就是调用耗时的操作
    await asyncio.sleep(x)
    return "Done after {}s".format(x)


start = now()

tl = [asyncio.ensure_future(do_some_work(random.randint(0, 5))) for i in range(10)]  # task list
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tl))

print("Time:", now() - start)  # Time: 5.0044331550598145

不在main协程函数里处理结果,直接返回await的内容,那么最外层的run_until_complete将会返回main协程的结果。 将上述的代码更改为:

import asyncio
import time

now = lambda: time.time()


async def do_some_work(x):
    print("waiting:", x)
    await asyncio.sleep(x)
    return "Done after {}s".format(x)


async def main():
    coroutine1 = do_some_work(1)
    coroutine2 = do_some_work(2)
    coroutine3 = do_some_work(4)
    tasks = [
        asyncio.ensure_future(coroutine1),
        asyncio.ensure_future(coroutine2),
        asyncio.ensure_future(coroutine3)
    ]
    # return await asyncio.gather(*tasks) # gather方式
    return await asyncio.wait(tasks)  # wait方式


start = now()

loop = asyncio.get_event_loop()
# results = loop.run_until_complete(main()) # gather 方式
# for result in results:
#     print("Task ret:", result)

done, pending = loop.run_until_complete(main())  # wait方式
for task in done:
    print("Task ret:", task.result())

print("Time:", now() - start)

可等待对象 await

可等待对象,await,如果一个对象可以在 await 语句中使用,那么它就是 可等待 对象。许多 asyncio API 都被设计为接受可等待对象。
可等待 对象有三种主要类型: 协程, 任务 和 Future.

  1. 协程定义: async def
  2. 任务 task ,通过 asyncio.create_task() 打包为一个 task
  3. Future 对象,表示一个异步操作的 最终结果。

协程停止

import asyncio
import time
import random

now = lambda: time.time()


async def do_some_work(x):
    print("Waiting:", x)
    await asyncio.sleep(x)
    return "Done after {}s".format(x)


tasks = [asyncio.ensure_future(do_some_work(random.randint(0, 3))) for i in range(3)]

start = now()

loop = asyncio.get_event_loop()
try:
    loop.run_until_complete(asyncio.wait(tasks))
except KeyboardInterrupt as e:
    print(asyncio.Task.all_tasks())
    for task in asyncio.Task.all_tasks():
        print(task.cancel())
    loop.stop()
    loop.run_forever()
finally:
    loop.close()

print("Time:", now() - start)

启动事件循环之后,马上ctrl+c,会触发run_until_complete的执行异常 KeyBorardInterrupt。然后通过循环asyncio.Task取消future。可以看到输出如下:

Waiting: 2
Waiting: 0
Waiting: 1
{<Task finished coro=<do_some_work() done, defined at test.py:8>
 result='Done after 0s'>, <Task pending coro=<do_some_work() running at test.py:10> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x0000019106B23A68>()]> cb=[_wait.<
locals>._on_completion() at C:\Users\**\AppData\Local\Programs\Python\Python37\lib\asyncio\tasks.py:440]>, <Task p
ending coro=<wait() running at C:\Users\**\AppData\Local\Programs\Python\Python37\lib\asyncio\tasks.py:363> wait_f
or=<Future pending cb=[<TaskWakeupMethWrapper object at 0x0000019106B23B88>()]>>, <Task pending coro=<do_some_work() r
unning at test.py:10> wait_for=<Future pending cb=[<TaskWakeupMe
thWrapper object at 0x0000019106AAF7F8>()]> cb=[_wait.<locals>._on_completion() at C:\Users\**\AppData\Local\Progr
ams\Python\Python37\lib\asyncio\tasks.py:440]>}
False
True
True
True
Time: 1.001704216003418

True表示cannel成功,loop stop之后还需要再次开启事件循环,最后在close,不然还会抛出异常

循环task,逐个cancel是一种方案,可是正如上面我们把task的列表封装在main函数中,main函数外进行事件循环的调用。这个时候,main相当于最外出的一个task,那么处理包装的main函数即可。

不同线程的事件循环

很多时候,我们的事件循环用于注册协程,而有的协程需要动态的添加到事件循环中。一个简单的方式就是使用多线程。当前线程创建一个事件循环,然后在新建一个线程,在新线程中启动事件循环。当前线程不会被block。

import asyncio
from threading import Thread
import time

now = lambda: time.time()


def start_loop(loop):
    asyncio.set_event_loop(loop)
    loop.run_forever()


def more_work(x):
    print('More work {}'.format(x))
    time.sleep(x)
    print('Finished more work {}'.format(x))


start = now()
loop = asyncio.new_event_loop()
t = Thread(target=start_loop, args=(loop,))
t.start()
print('TIME: {}'.format(time.time() - start))

loop.call_soon_threadsafe(more_work, 6)
loop.call_soon_threadsafe(more_work, 3)


启动上述代码之后,当前线程不会被block,新线程中会按照顺序执行call_soon_threadsafe方法注册的more_work方法, 后者因为time.sleep操作是同步阻塞的,因此运行完毕more_work需要大致6 + 3

非阻塞版本

import asyncio
import time
from threading import Thread

now = lambda: time.time()


def start_loop(loop):
    asyncio.set_event_loop(loop)
    loop.run_forever()


async def do_some_work(x):
    print('Waiting {}'.format(x))
    await asyncio.sleep(x)
    print('Done after {}s'.format(x))


def more_work(x):
    print('More work {}'.format(x))
    time.sleep(x)
    print('Finished more work {}'.format(x))


start = now()
new_loop = asyncio.new_event_loop()
t = Thread(target=start_loop, args=(new_loop,))
t.start()
print('TIME: {}'.format(time.time() - start))

asyncio.run_coroutine_threadsafe(do_some_work(6), new_loop)
asyncio.run_coroutine_threadsafe(do_some_work(4), new_loop)

上述的例子,主线程中创建一个new_loop,然后在另外的子线程中开启一个无限事件循环。 主线程通过run_coroutine_threadsafe新注册协程对象。这样就能在子线程中进行事件循环的并发操作,同时主线程又不会被block。一共执行的时间大概在6s左右。

关于同步、异步、阻塞、非阻塞的理解

“阻塞”与"非阻塞"与"同步"与“异步"不能简单的从字面理解,提供一个从分布式系统角度的回答。
1.同步与异步
同步和异步关注的是消息通信机制 (synchronous communication/ asynchronous communication)
所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回。但是一旦调用返回,就得到返回值了。
换句话说,就是由调用者主动等待这个调用的结果。

而异步则是相反,调用*在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在调用发出后,被调用者*通过状态、通知来通知调用者,或通过回调函数处理这个调用。

典型的异步编程模型比如Node.js

举个通俗的例子:
你打电话问书店老板有没有《分布式系统》这本书,如果是同步通信机制,书店老板会说,你稍等,”我查一下",然后开始查啊查,等查好了(可能是5秒,也可能是一天)告诉你结果(返回结果)。
而异步通信机制,书店老板直接告诉你我查一下啊,查好了打电话给你,然后直接挂电话了(不返回结果)。然后查好了,他会主动打电话给你。在这里老板通过“回电”这种方式来回调。

\2. 阻塞与非阻塞
阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态.

阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。
非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。

还是上面的例子,
你打电话问书店老板有没有《分布式系统》这本书,你如果是阻塞式调用,你会一直把自己“挂起”,直到得到这本书有没有的结果,如果是非阻塞式调用,你不管老板有没有告诉你,你自己先一边去玩了, 当然你也要偶尔过几分钟check一下老板有没有返回结果。
在这里阻塞与非阻塞与是否同步异步无关。跟老板通过什么方式回答你结果无关。

如果是关心阻塞 IO/ 异步 IO, 参考 Unix Network Programming View Book

--

还是2014年写的以解释概念为主,主要是同步异步 阻塞和非阻塞会被用在不同层面上,可能会有不准确的地方,并没有针对 阻塞 IO/ 异步 IO 等进行讨论,大家可以后续看看这两个回答:

怎样理解阻塞非阻塞与同步异步的区别?

怎样理解阻塞非阻塞与同步异步的区别?

参考

Coroutines and Tasks

python中重要的模块--asyncio

python异步asyncio模块的使用

怎样理解阻塞非阻塞与同步异步的区别?

posted @ 2019-08-11 15:27  写bug的日子  阅读(355)  评论(0编辑  收藏  举报