asyncio是什么(什么情况下需要使用asyncio)
同步代码(synchrnous code)我们都很熟悉,就是运行完一个步骤再运行下一个。要在同步代码里面实现"同时"运行多个任务,最简单也是最直观地方式就是运行多个 threads 或者多个 processes。这个层次的『同时运行』多个任务,是操作系统协助完成的。
异步编程(asynchrnous code)与之不同的是,只使用一个进程,不使用 threads,但是也能实现"同时"运行多个任务(这里的任务其实就是函数)。
异步函数必要的可以暂停,把运行的权利交给其他函数。等到时机恰当,又可以恢复之前的状态继续运行。
可交给 asyncio 执行的任务,称为协程(coroutine)。一个协程可以放弃执行,把机会让给其它协程(即 yield from 或 await)。
异步编程的实现
asyncio提供的框架以事件循环(event loop)为中心,程序开启一个无限的循环,程序会把一些函数注册到事件循环上。当满足事件发生的时候,调用相应的协程函数。
几个核心概念
事件循环:一种等待程序分配事件或消息的编程架构
future是一个数据结构,表示还未完成的工作结果。事件循环可以监视Future对象是否完成。
task是Future的一个子类,它知道如何包装和管理一个协程的执行。
异步方法(异步函数,协程(Coroutine))
async def async_double(x):
return 2 * x
要调用异步函数,必须使用await关键字。
async def print_double(x): # 同步函数不能加async
print(await async_double(x)) #
最简功能代码
import asyncio
async def foo():
print("这是一个协程")
if __name__ == '__main__':
loop = asyncio.get_event_loop() # 定义loop,事件循环的应用
try:
print("开始运行协程")
coro = foo()
print("进入事件循环")
loop.run_until_complete(coro) # 默认循环run_until_complete(coro)方法用这个协程启动循环
# 协程返回时这个方法将停止循环。
finally:
print("关闭事件循环")
loop.close()
run_until_complete的参数是一个futrue对象。当传入一个协程,其内部会自动封装成task,其中task是Future的子类。
协程调用协程
import asyncio
async def main():
print("主协程")
print("等待result1协程运行")
res1 = await result1() # result1执行完了才会运行result2
print("等待result2协程运行")
res2 = await result2(res1)
return (res1,res2)
async def result1():
print("这是result1协程")
return "result1"
async def result2(arg):
print("这是result2协程")
return f"result2接收了一个参数,{arg}"
if __name__ == '__main__':
loop = asyncio.get_event_loop()
try:
result = loop.run_until_complete(main())
print(f"获取返回值:{result}")
finally:
print("关闭事件循环")
loop.close()
协程中调用普通函数
在协程中可以通过一些方法去调用普通的函数。可以使用的关键字有call_soon,call_later,call_at。
import asyncio
import functools
def callback(args, *, kwargs="defalut"):
print(f"普通函数做为回调函数,获取参数:{args},{kwargs}")
def call_back_at(n, loop):
print(f"callback {n} 运行时间点{loop.time()}")
async def fun1():
print('before sleep')
await asyncio.sleep(5)
print('after sleep')
async def fun2():
print('before fun2 sleep')
await asyncio.sleep(5)
print('after fun2 sleep')
async def main(loop):
print("注册callback")
now = loop.time()
print('当前时间', now)
loop.call_soon(callback, 3) # 协程后最先执行
loop.call_later(3, callback, 2) # 先写后执行
loop.call_later(0.1, callback, 1) # 后写先执行,只延迟1s
loop.call_at(now, call_back_at, 1, loop)
await fun1()
await fun2() # 协程会阻塞后面调用函数
# 基于callback重新定义wrapped函数(参数不同)
wrapped = functools.partial(callback, kwargs="not defalut")
loop.call_soon(wrapped, 2)
loop.call_at(now, call_back_at, 2, loop) # now已过期,也排call_soon后面
await asyncio.sleep(0.2)
if __name__ == '__main__':
loop = asyncio.get_event_loop()
try:
loop.run_until_complete(main(loop))
finally:
loop.close()
Future
获取Futrue里的结果
future表示还没有完成的工作结果。事件循环可以通过监视一个future对象的状态来指示它已经完成。future对象有几个状态:
Pending
Running
Done
Cancelled
创建future的时候,task为pending,事件循环调用执行的时候当然就是running,调用完毕自然就是done,如果需要停止事件循环,就需要先把task取消,状态为cancel。
import asyncio
def foo(future, result):
print(f"此时future的状态:{future}")
print(f"设置future的结果:{result}")
future.set_result(result)
print(f"此时future的状态:{future}")
if __name__ == '__main__':
loop = asyncio.get_event_loop()
try:
all_done = asyncio.Future()
loop.call_soon(foo, all_done, "Future is done!")
print("进入事件循环")
result = loop.run_until_complete(all_done)
print("返回结果", result)
finally:
print("关闭事件循环")
loop.close()
print("获取future的结果", all_done.result())
Future回调
Future 在完成的时候可以执行一些回调函数,回调函数按注册时的顺序进行调用:
组合协程
一系列的协程可以通过await链式的调用,但是有的时候我们需要在一个协程里等待多个协程,比如我们在一个协程里等待1000个异步网络请求,对于访问次序有没有要求的时候,就可以使用另外的关键字wait或gather来解决了。wait可以暂停一个协程,直到后台操作完成。
等待多个协程
tasks = [num(i) for i in range(10)]
complete, pending = await asyncio.wait(tasks, timeout=0.5)
wait的使用:
在内部wait()使用一个set保存它创建的Task实例。因为set是无序的所以这也就是我们的任务不是顺序执行的原因。wait的返回值是一个元组,包括两个集合,分别表示已完成和未完成的任务。wait第二个参数为一个超时值
达到这个超时时间后,未完成的任务状态变为pending,
gather的使用
gather的作用和wait类似不同的是。
1.gather任务无法取消。
2.返回值是一个结果列表(complete,因为无法取消,只能是完成)
3.可以按照传入参数的顺序,顺序排列任务。(wait任务排列是随机的)
gather通常被用来阶段性的一个操作,做完第一步才能做第二步
1.step1和step2是并行运行的。
2.gather会等待最耗时的那个完成之后才返回结果,耗时总时间取决于其中任务最长时间的那个。
tasks = [num(i) for i in range(10)]
complete = await asyncio.gather(*tasks)
参考文献
https://zhuanlan.zhihu.com/p/59671241
异步任务和定时任务的区别
在开发系统的过程中,通常会考虑到系统的性能问题,提升系统性能的一个重要思想就是“串行”改“并行”。说起“并行”自然离不开“异步”。
量化开发中的多策略并行执行用到的就是异步
定时任务,项目开发中经常需要执行一些固定时间执行的任务,比如需要在每天凌晨的时候,分析一次前一天的日志信息
量化开发中定时执行策略,用到的就是定时任务,单纯的定时任务是串行的(同步任务),如果定时任务在同一时间点需要同时执行多个,就涉及到异步