在协程中调用普通函数,可以使用关键字:call_soon, call_later, call_at
1、loop.call_soon(callback, *args, context=None) 从字面上看是调用立即返回。在下一个迭代的事件循环中立即调用回调函数,大部分的回调函数支持位置参数,而不支持关键字参数,如果想用关键字参数,可以使用functools.partial()方法对回调函数进一步封装。可选关键字context允许指定要运行的回调的自定义contextvars.Context,当没有提供上下文时使用当前上下文。在Python3.7中,asyncio协程加入了对上下文的支持。使用上下文可以在一些场景下隐式地传递变量,比如数据库链接session等,而不需要在所有方法调用显式地传递这些变量。
import asyncio
import functools
def call_back(args, kwargs="default"):
print(f"普通函数作为回调函数,获取参数:{args}, {kwargs}")
async def main():
print("注册callback函数")
loop.call_soon(call_back, 1)
wrapped = functools.partial(call_back, kwargs="xxoo") # 使用偏函数传递关键字参数
loop.call_soon(wrapped, 2)
await asyncio.sleep(0.5)
if __name__ == "__main__":
loop = asyncio.get_event_loop()
try:
loop.run_until_complete(main())
except:
raise
finally:
loop.close()
注册callback函数
普通函数作为回调函数,获取参数:1, default
普通函数作为回调函数,获取参数:2, xxoo
如果不想立即调用一个函数,可以使用call_later延时调用一个函数。
2、loop.call_later(delay, callback, *args, context=None) 事件循环在delay一段时间之后才执行callback函数
import asyncio
def call_back(index, x, y):
print(f"[Call_back--{index}]:{x} + {y} = {x + y}")
async def main():
print("注册callback函数")
loop.call_later(0.1, call_back, 1, 1, 2)
loop.call_later(0.3, call_back, 2, 3, 4)
loop.call_later(0.2, call_back, 3, 5, 7)
loop.call_soon(call_back, "call_soon 方法", 9, 9) # call_soon方法会在call_later之前执行,与其位置无关
await asyncio.sleep(0.5)
if __name__ == "__main__":
loop = asyncio.get_event_loop()
try:
loop.run_until_complete(main())
except:
raise
finally:
loop.close()
注册callback函数
[Call_back--call_soon 方法]:9 + 9 = 18
[Call_back--1]:1 + 2 = 3
[Call_back--3]:5 + 7 = 12
[Call_back--2]:3 + 4 = 7
3、loop.call_at(when, callback, *args, contex=None),when参数代表一个单调时间,和我们说的系统时间有点差异。这里的时间是指事件循环的内部时间,可以通过loop.time()获取,然后在此基础上进行操作。后面的几个参数跟之前的一样。 实际上call_later内部就是调用的call_at。
import asyncio
def call_back(index):
print(f"[Call_back--{index}]: The running time point is {loop.time()}")
async def main():
now = loop.time()
print(f"当前的内部时间:{now}")
print("注册callback")
loop.call_at(now + 0.2, call_back, "c_b-1")
loop.call_at(now + 0.1, call_back, "c_b-2")
loop.call_soon(call_back, "call_soon方法")
await asyncio.sleep(0.5)
if __name__ == "__main__":
loop = asyncio.get_event_loop()
try:
loop.run_until_complete(main())
except:
raise
finally:
loop.close()
当前的内部时间:13234.680823788
注册callback
[Call_back--call_soon方法]: The running time point is 13234.682998608
[Call_back--c_b-2]: The running time point is 13234.781687337
[Call_back--c_b-1]: The running time point is 13234.881544616
协程的停止
future表示还没有完成的工作结果,事件循环可以监视一个future对象的状态来指示它已经完成。future有以下几个状态:
创建future的时候,task是pending状态,事件循环调用执行的时候,此时task是running状态,调用完毕后是done状态,如果需要停止事件循环,就需要先把task取消,状态是cancelled,可以通过asyncio.Task获取事件循环的task
import asyncio
def call_back(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:
future = asyncio.Future()
loop.call_soon(call_back, future, "Future is done!")
print("进入事件循环")
result = loop.run_until_complete(future)
print(f"返回的结果:{result}")
except:
raise
finally:
loop.close()
print(f"获取future的结果{future.result()}")
从结果中可以看出,调用set_result之后future的状态从pending变为finished,Future的实例future会保留提供给方法的结果,可以在后续使用。
loop.run_until_complete(future)运行直到将来(一个实例Future)完成。
loop.run_forever()
loop.stop()停止事件循环。
loop.is_running()
loop.is_closed()
loop.close()关闭事件循环。
在了解了上述时间循环的相关方法后,再看协程的停止。
import asyncio
async def do_some_work(x):
print('Waiting: ', x)
await asyncio.sleep(x)
return 'Done after {}s'.format(x)
coroutine1 = do_some_work(1) # 协程对象
coroutine2 = do_some_work(2)
coroutine3 = do_some_work(3)
tasks = [
asyncio.ensure_future(coroutine1), # 创建task
asyncio.ensure_future(coroutine2),
asyncio.ensure_future(coroutine3)
]
loop = asyncio.get_event_loop()
try:
# 启动事件循环后马上终止程序后,会触发run_until_complete的执行异常 KeyBorardInterrupt
loop.run_until_complete(asyncio.wait(tasks))
# 抛出异常后遍历事件循环里的所有task(asyncio.Task.all_tasks()),并取消任务,停止时间循环
except KeyboardInterrupt as e:
print("查看事件循环里的所有任务:", asyncio.Task.all_tasks())
for task in asyncio.Task.all_tasks():
print("任务是否cancelled??", task.cancel()) # 取消task
loop.stop() # 停止事件循环,配合run_forever()退出事件循环
loop.run_forever()
finally:
loop.close() # 关闭事件循环
再启动程序后立即终止程序,运行结果如下:
True表示cannel成功,loop stop之后还需要再次开启事件循环,最后在close,不然还会抛出异常:
Task was destroyed but it is pending!
task:
import asyncio
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(3)
tasks = [
asyncio.ensure_future(coroutine1), # 创建task
asyncio.ensure_future(coroutine2),
asyncio.ensure_future(coroutine3)
]
done, pending = await asyncio.wait(tasks)
for task in done:
print("Task result: ", task.result())
if __name__ == "__main__":
loop = asyncio.get_event_loop()
try:
# 启动事件循环后马上终止程序后,会触发run_until_complete的执行异常 KeyBorardInterrupt
loop.run_until_complete(main())
# 取消任务,包括最外层的main
except KeyboardInterrupt as e:
print(asyncio.Task.all_tasks())
# 解包后一起cancel
print(asyncio.gather(*asyncio.Task.all_tasks()).cancel()) # 取消task
loop.stop() # 停止事件循环,配合run_forever()退出事件循环
loop.run_forever()
finally:
loop.close() # 关闭事件循环
使用协程嵌套的方式管理这三个异步协程,这是main相当于最外层的task,运行结果如下:
获取事件循环、设置或创建事件循环:
asyncio.get_running_loop()返回当前 OS 线程中正在运行的事件循环。
asyncio.get_event_loop()获取当前事件循环。
asyncio.set_event_loop(loop)将 loop 设置为当前 OS 线程的当前事件循环。
asyncio.new_event_loop()创建一个新的事件循环。
loop.call_soon_threadsafe(callback,*args,context=None)call_soon的变种,线程安全,用于调度来自另一个线程的回调函数
import asyncio
import time
from threading import Thread
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))
new_loop = asyncio.new_event_loop()
t = Thread(target=start_loop, args=(new_loop,))
t.start()
print("The main thread is running...")
new_loop.call_soon_threadsafe(more_work, 6) # 调度一个函数
new_loop.call_soon_threadsafe(more_work, 3)
# 运行结果:启动上述代码后,主线程不会被堵塞,新线程会按照顺序执行call_soon_threadsafe方法注册的more_work方法,后者因为time.sleep(x)
# 操作是同步阻塞,因此运行完毕需要大致6+3秒
The main thread is running...
More work 6
Finished more work 6
More work 3
Finished more work 3
asyncio.run_coroutine_threadsafe(coro,loop )将协程提交给给定的事件循环。线程安全的。异步执行。
此函数旨在从与运行事件循环的OS线程不同的OS线程调用。例:
# Create a coroutine
coro = asyncio.sleep(1, result=3)
# Submit the coroutine to a given loop
future = asyncio.run_coroutine_threadsafe(coro, loop)
# Wait for the result with an optional timeout argument
assert future.result(timeout) == 3
import asyncio
import time
from threading import Thread
def start_loop(loop):
asyncio.set_event_loop(loop)
loop.run_forever()
async def more_work(x):
print('More work {}'.format(x))
await asyncio.sleep(x)
print('Finished more work {}'.format(x))
return f"the task conducts about {x}s"
new_loop = asyncio.new_event_loop()
t = Thread(target=start_loop, args=(new_loop,))
t.start()
print("The main thread is running...")
future1 = asyncio.run_coroutine_threadsafe(more_work(6), new_loop) # 调度一个异步协程,返回一个future对象来获取结果
future2 = asyncio.run_coroutine_threadsafe(more_work(3), new_loop)
print(future1.result(), future2.result())
The main thread is running...
More work 6
More work 3
Finished more work 3
Finished more work 6
the task conducts about 6s the task conducts about 3s
多线程中开启多个事件循环: 协程多任务通常是运行在同一进程的单线程环境中,但是如果想要将基于asyncio的协程运行在不同的线程中,可以通过主线程中的asyncio.new_event_loop()创建一个loop,在子线程中使用asyncio.set_event_loop(loop)为子线程设置时间循环。如下例:
import asyncio
from concurrent.futures import ThreadPoolExecutor
def thread_loop_task(loop):
# 为子线程设置自己的事件循环
asyncio.set_event_loop(loop)
async def task_1(index, delay):
print("The task_1 is ready...")
print(f"The task_1 conducts on loop:{id(loop)}")
await asyncio.sleep(delay)
print("The task_1 is finished!")
async def task_2(index, delay):
print("The task_2 is ready...")
print(f"The task_2 conducts on loop:{id(loop)}")
await asyncio.sleep(delay)
print("The task_2 is finished!")
futures = asyncio.gather(task_1(1, 1), task_2(2, 2))
loop.run_until_complete(futures)
if __name__ == "__main__":
# 创建一个事件循环thread_loop
print("Create a new loop for child thread!")
thread_loop = asyncio.new_event_loop()
# 将thread_loop作为参数传递给子线程
executor = ThreadPoolExecutor()
executor.submit(thread_loop_task, thread_loop)
print("The main thread is not blocked ....")
main_loop = asyncio.get_event_loop()
async def main_task():
for _ in range(2):
print(f"The main thread conducts on loop:{id(main_loop)}")
await asyncio.sleep(1)
main_loop.run_until_complete(main_task())
Create a new loop for child thread!
The main thread is not blocked ....
The main thread conducts on loop:140221310038928
The task_1 is ready...
The task_1 conducts on loop:140221309995384
The task_2 is ready...
The task_2 conducts on loop:140221309995384
The main thread conducts on loop:140221310038928
The task_1 is finished!
The task_2 is finished!
主线程和子线程通过queue实现信息的传递:
import asyncio,time,os,sys,queue,threading
"""
call_soon , call_soon_threadsafe 是同步的
asyncio.run_coroutine_threadsafe(coro, loop) -> 对应 asyncio.ensure_future事件循环中,异步执行.
"""
# 在子线程中执行一个事件循环 , 注意需要一个新的事件循环
def thread_loop(loop:asyncio.AbstractEventLoop):
print('线程开启 tid:', threading.currentThread().ident)
asyncio.set_event_loop(loop) # 设置一个新的事件循环
loop.run_forever()
async def func(x, q):
print('func :', x, ',time:', time.ctime(), ',tid:', threading.currentThread().ident)
await asyncio.sleep(x)
q.put(x)
q = queue.Queue()
lp = asyncio.new_event_loop() # 新建一个事件循环 , 如果使用默认的,则不能放入子线程
t = threading.Thread(target=thread_loop,args=(lp,))
t.start()
co1 = func(2,q) # 2个协程
co2 = func(3,q)
asyncio.run_coroutine_threadsafe(co1,lp) # 开始调度在子线程中的事件循环
asyncio.run_coroutine_threadsafe(co2,lp)
print('开始事件:',time.ctime())
while True:
x = q.get()
print('main :',x,',time:',time.ctime())
线程开启 tid: 140221068859136
开始事件: Tue Jul 2 15:18:10 2019
func : 2 ,time: Tue Jul 2 15:18:10 2019 ,tid: 140221068859136
func : 3 ,time: Tue Jul 2 15:18:10 2019 ,tid: 140221068859136
main : 2 ,time: Tue Jul 2 15:18:12 2019
main : 3 ,time: Tue Jul 2 15:18:13 2019
停止子线程:
如果需要停止程序,直接ctrl + c就行,这时会抛出一个KeyboardInterrupt异常,我们捕获这个异常,然后loop.stop()。但是在实际过程中,这种方式并不好使,虽然主线程捕获了KeyboardInterrupt这个异常,但是,子线程并没有退出,为了解决这个问题,可以设置子线程为守护线程,当主线程结束后,子线程也随即退出。
守护线程setDaemon()方法。 主线程A中,创建了子线程B,并且在主线程A中调用了B.setDaemon(),这个的意思是,把主线程A设置为守护线程,这时候,要是主线程A执行结束了,就不管子线程B是否完成,一并和主线程A退出.这就是setDaemon方法的含义,这基本和join是相反的。此外,还有个要特别注意的:必须在start() 方法调用之前设置,如果不设置为守护线程,程序会被无限挂起。
import asyncio
import time
from threading import Thread
def start_loop(loop):
asyncio.set_event_loop(loop)
loop.run_forever()
async def task(index, x):
print(f"The task_{index} will waite {x}s")
await asyncio.sleep(x)
print(f"Done after {x}s")
new_loop = asyncio.new_event_loop()
t = Thread(target=start_loop, args=(new_loop,))
# t.setDaemon(True) # 设置为守护线程
t.start()
print("The main thread is running...")
try:
while 1:
asyncio.run_coroutine_threadsafe(task(1, 1), new_loop)
time.sleep(1)
asyncio.run_coroutine_threadsafe(task(2, 2), new_loop)
except KeyboardInterrupt as e:
print(f"---------KeyboardInterrupt:{e}")
new_loop.stop()
print("------xxoo-------")
结果如下