asyncio协程

asyncio协程

3.4版本加入标准库。
asyncio底层基于selectors实现,看似库,其实就是一个框架,包含异步IO、事件循环、协程、任务等内容。

问题引出

  • 基于不同程序交替执行
  1. 多线程版本

    import threading
    import time
    
    def a():
        for x in range(3):
            time.sleep(0.001)
            print(x)
    
    def b():
        for x in "abc":
            time.sleep(0.001)
            print(x)
    
    threading.Thread(target=a,name="a").start()
    threading.Thread(target=b,name="b").start()
    

    asyncio_001

  2. 多进程版本

    import multiprocessing
    import time
    
    def a():
        for x in range(3):
            time.sleep(0.1)
            print(x)
    
    def b():
        for x in "abc":
            time.sleep(0.1)
            print(x)
    
    if __name__ == '__main__':
        pa = multiprocessing.Process(target=a,name="a")
        pb = multiprocessing.Process(target=b,name="b")
        pa.start()
        pb.start()
    

    asyncio_002

  3. 生成器版本

    import multiprocessing
    import time
    
    def a():
        for x in range(3):
            print(x)
            yield
    
    def b():
        for x in "abc":
            print(x)
            yield
    
    x = a()
    y = b()
    
    for i in range(3):
        next(x)
        next(y)
    

    asyncio_003

  • 上例在一个线程内通过生成器完成了调度,让两个函数都有机会执行,这样的调度不是操作系统的进程、线程完成的,而是用户自己设计出来的。
    1. 需要使用yield来让出控制权
    2. 需要使用循环帮助交替执行

事件循环

事件循环是asyncio提供的核心运行机制

常用方法 含义
asyncio.get_event_loop() 返回一个事件循环对象,是asyncio.BaseEventLoop的实例
AbstractEventLoop.stop() 停止运行事件循环
AbstractEventLoop.run_forever() 一直运行,知道stop()
AbstractEventLoop.run_until_complete(future) 运行直至Future对象运行完。返回Futrue的结果。参数可以是Future类或子类Task的对象。如果是协程对象也会被封装成Task对象
AbstractEventLoop.close() 关闭事件循环
AbstractEventLoop.is_running() 返回事件循环的是否运行
AbstractEventLoop.close() 关闭事件循环
AbstractEventLoop.create_task(coro) 使用协程对象创建任务对象

协程

  • 协程不是进程、也不是线程,它是用户空间调度来完成并发处理的方式

  • 进程、线程由操作系统完成调度,而协程是线程内完成调度。它不需要更多的线程,自然也没有多线程切换带来的开销

  • 协程是非抢占式调度,只有一个协程主动让出控制权,另一个协程才会被调度

  • 协程也不需要使用锁机制,因为是在同一个线程中执行

  • 多CPU下,可以使用进程和协程配合,即能进程并发又能发挥协程在单线程中的优势

  • Python中协程是基于生成器的

  • asyncio.iscoroutine(obj)判断是不是协程对象

  • asyncio.iscoroutinefunction(func)判断是不是协程函数

  • Future和concurrent.futures.Future类似。通过Future对象可以了解任务执行的状态数据。

  • 事件循环来监听Future对象是否完成。

  • Task任务:Task类是Future的子类,它的作用就是把协程包装成一个Future对象。

协程的使用

  • 3.4引入asyncio,使用装饰器,将生成器函数转换成协程函数,就可以在事件循环中执行了。

  • 简单示例

import asyncio

@asyncio.coroutine
def xdd(x): #协程函数
    for i in range(3):
        print("xdd {}".format(i))
        yield from asyncio.sleep(x)
    return "result = {}".format(1000)

#事件循环
loop = asyncio.get_event_loop()

# 本质就是tasks的ensure_future,把协程包装进一个Future对象中,并使用create_task返回一个task
future = asyncio.ensure_future(xdd(1))

# 内部会调用ensure_future,内部会执行loop.run_forever()
loop.run_until_complete(future)

print(" -"* 30)
loop.close()
print(future.result()) #拿return值
print("====end=======")

asyncio_004

  1. ensure_future(coro_or_future):
    • 如果参数已经是future直接返回
    • 如果是协程,则使用loop.create_task()创建task,并返回task
  • 可以使用create_task函数得到task对象
import asyncio

@asyncio.coroutine
def xdd(x): #协程函数
    for i in range(3):
        print("xdd {}".format(i))
        yield from asyncio.sleep(x)
    return "result = {}".format(1000)

x = xdd(1)
print(x)

# 事件循环
loop = asyncio.get_event_loop()

# create_task返回一个task
task = loop.create_task(x)
print(1,task) #pending
#内部会调用ensure_future,内部会执行loop.run_forever()
loop.run_until_complete(task)
print(2,task) #finished

print("- "* 30)
loop.close()
print(task.result()) #拿return值
print("======end=======")

asyncio_005

  • 如果对函数的返回值不在乎,上面代码可以如下实现:
import asyncio

@asyncio.coroutine
def xdd(x): #协程函数
    for i in range(3):
        print("xdd {}".format(i))
        yield from asyncio.sleep(x)
    return "result = {}".format(1000)

#事件循环
loop = asyncio.get_event_loop()

# 本质就是tasks的ensure_future,把协程包装进一个Future对象中,并返回一个task
# 内部会调用ensure_future,内部会执行loop.run_forever()
loop.run_until_complete(xdd(0.1)) #包装协程为task

print(" -"* 30)
loop.close()
print("====end=======")

asyncio_006

上例中,似乎拿不到xdd(1)这个协程对象的结果future了,run_until_complete函数的返回值就是其参数future对象的返回结果。
future对象都可以调用add_done_callback(fn)增加回调函数,回调函数是单参的,参数就是future对象。
代码如下:

import asyncio

@asyncio.coroutine
def xdd(x): #协程函数
    for i in range(3):
        print("xdd {}".format(i))
        yield from asyncio.sleep(x)
    return "result = {}".format(1000)

# 回调函数,参数必须是future
def callback(future):
    print("in callback. future = {}".format(future))
    print(future.result())

# 事件循环
loop = asyncio.get_event_loop()

# create_task返回一个task
task = loop.create_task(xdd(1))
# 添加回调函数
task.add_done_callback(callback)
print(1,task) #pending
# 内部会调用ensure_future,内部会执行loop.run_forever()
loop.run_until_complete(task)
print(2,task)

print(" -"* 30)
loop.close()

print("====end=======")

asyncio_007

  • 多个任务执行
import asyncio

@asyncio.coroutine
def xdd(x): #协程函数
    for i in range(3):
        print("xdd {}".format(i))
        yield from asyncio.sleep(x)
    return "result = {}".format(1000)

@asyncio.coroutine
def b():
    for x in range(10):
        print("b.x",x)
        yield
    return "b.finished"

# 回调函数,参数必须是future
def callback(future):
    print("in callback. future = {}".format(future))
    print(future.result())

print(asyncio.iscoroutinefunction(xdd),asyncio.iscoroutinefunction(b))

t1 = xdd(0.001)
t2 = b()
print(asyncio.iscoroutine(t1),asyncio.iscoroutine(t2))

#事件大循环
loop = asyncio.get_event_loop()

# 先构建t1为task1,为其添加回调函数
task1 = loop.create_task(t1)
task1.add_done_callback(callback)
tasks = [task1,t2]

# asyncio.wait 会迭代列表中的对象并封装成(f1,f2),返回一个协程对象f
# 循环执行f,它内部等价于yield from {f1,f2}
rt = loop.run_until_complete(asyncio.wait(tasks))
print(rt) #全部结果,全部完成才能看
loop.close()

asyncio_008

  • 注意:run_until_complete方法的返回结果,必须所有任务执行完才能看。

协程的新语法

  • 3.5版本开始,Python提供关键字async,await,在语言上原生支持协程。

  • 简单演示

import asyncio

async def xdd():
    for i in range(3):
        print("xdd {}".format(i))
        await asyncio.sleep(0.5)

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

asyncio_009

  • async def用来定义协程函数。

  • iscoroutinefunction()判断是否是协程函数,True表示是协程函数

  • 协程函数中可以不包含await、async关键字,但不能使用yield关键字。

  • 如同生成器函数调用返回生成器对象一样,协程函数调用也会返回一个对象称为协程对象,iscoroutine()返回True,判断对象是否是协程对象。

  • await语句之后是awaitable对象,可以是协程或者实现了__await__()方法的对象。await会暂停当前协程执行,使loop调度其他协程。

  • 示例:

import asyncio

@asyncio.coroutine
def w():
    yield

async def a():
    for x in range(3):
        print("a.x",x)
        await w()
    return "a.finished"

async def b():
    for x in range(5):
        print("b.x",x)
        await w()
    return "b.finished"

print(asyncio.iscoroutinefunction(a),asyncio.iscoroutinefunction(b))

t1 = a()
t2 = b()
print(asyncio.iscoroutine(t1),asyncio.iscoroutine(t2))

def cb(future):
    print(future.result())

#事件大循环
loop = asyncio.get_event_loop()
fs = set()
for t in [t1,t2]:
    f = asyncio.ensure_future(t)
    f.add_done_callback(cb)
    fs.add(f)

# asyncio.wait 会迭代列表中的对象并封装成(f1,f2),返回一个协程对象f
# 循环执行f,它内部等价于yield from {f1,f2}
results = loop.run_until_complete(asyncio.wait(fs))
loop.close()

print(results) #全部结果,全部完成才能看

asyncio_010

TCP Echo Server举例

  • 单聊版本
import asyncio
from asyncio.streams import StreamWriter,StreamReader

async def hander(reader:StreamReader,witer:StreamWriter):
    client = None
    try:
        print(server.sockets)
        client = witer.get_extra_info("peername")
        print("新加入了一个连接 {}".format(client))
        while True:
            data = await reader.read(1024)
            if data == b'':
                break
            print(data)
            msg = "{}-{}".format(client,data).encode()
            witer.write(msg)
    finally:
        witer.close()
        print("退出了一个连接 {}".format(client))

#获取事件大循环
loop = asyncio.get_event_loop()
#返回一个协程对象
conn = asyncio.start_server(hander,"127.0.0.1",3999,loop=loop)
#获取sever
server = loop.run_until_complete(conn)

try:
    loop.run_forever()
except KeyboardInterrupt:
    pass
finally:
    server.close()
    loop.run_until_complete(server.wait_closed())
    loop.close()
  • 群聊版本
import asyncio
from asyncio.streams import StreamWriter,StreamReader
import threading

class AsClentServer:
    def __init__(self,ip="127.0.0.1",post=3999):
        self.loop = asyncio.get_event_loop()
        self.ip = ip
        self.post = post
        self.witers = set()
        self.event = threading.Event()

    #启动服务
    def start(self):
        conn = asyncio.start_server(self.handler, self.ip, self.post, loop=self.loop)
        self.server = self.loop.run_until_complete(conn)
        threading.Thread(target=self.run,name="server",daemon=True).start()

    def run(self):
        try:
            self.loop.run_forever()
        except KeyboardInterrupt:
            pass
        finally:
            self.close()

    async def handler(self,reader:StreamReader,witer:StreamWriter):
        client = None
        self.witers.add(witer)
        try:
            client = witer.get_extra_info("peername")
            print("新加入了一个连接 {}".format(client))
            while not self.event.is_set():
                data = await reader.read(1024)
                if data == b'' or data == b'quit':
                    break
                print(data)
                msg = "{}-{}".format(client, data).encode()
                for i in self.witers:
                    i.write(msg)
        finally:
            witer.close()
            self.witers.remove(witer)
            print("退出了一个连接 {}".format(client))

    def close(self):
        self.event.set()
        for i in self.witers:
            i.close()
        self.witers.clear()
        if hasattr(self,"server"):
            self.server.close()
        try:
            self.loop.run_until_complete(self.server.wait_closed())
            self.loop.close()
        except:
            pass

    @classmethod
    def main(cls):
        cltserver = cls()
        cltserver.start()
        while True:
            cmd = input(">>>")
            if cmd == "quit":
                cltserver.close()
                break
            print(threading.enumerate())

if __name__ == '__main__':
    AsClentServer.main()

aiohttp库

异步Http客户端、服务端框架
安装$ pip install aiohttp
文档 https://aiohttp.readthedocs.io/en/stable/

  • HTTP Server简单示例
from aiohttp import web

async def indexhandle(request:web.Request):
    return web.Response(text=request.path,status=201)

async def handle(request:web.Request):
    print(request.match_info)
    print(request.query_string) # http://127.0.0.1:8888/1?name=12301
    return web.Response(text=request.match_info.get("id","0000"),status=200)

app = web.Application()
app.router.add_get("/",indexhandle) # http://127.0.0.1:8080/
app.router.add_get("/{id}",handle) #http://127.0.0.1:8080/12301
# app.add_routes([
#     web.get("/",indexhandle),
#     web.get("/{id}",handle)
# ])
web.run_app(app,host="0.0.0.0",port=3888)
  • HTTP Client
import asyncio
from aiohttp import ClientSession

async def get_html(url:str):
    async with ClientSession() as session:
        async with session.get(url) as res:
            print(res.status)
            print(await res.text())

url = "http://127.0.0.1:3888/2?age=20&name=jerry"
loop = asyncio.get_event_loop()
loop.run_until_complete(get_html(url))
loop.close()

猜你喜欢

转载自blog.csdn.net/u013008795/article/details/93381162