python多任务—协程(二)

在协程中调用普通函数,可以使用关键字: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有以下几个状态:

  • pending
  • running
  • one
  • cancelled

创建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()}")
进入事件循环
此时future的状态:<Future pending cb=[_run_until_complete_cb() at /home/xuzhao/anaconda3/lib/python3.7/asyncio/base_events.py:158]>
设置future的结果:Future is done!
此时future的状态:<Future finished result='Future is done!'>
返回的结果:Future is done!
获取future的结果Future is done!

从结果中可以看出,调用set_result之后future的状态从pending变为finished,Future的实例future会保留提供给方法的结果,可以在后续使用。

事件循环的运行与停止

loop.run_until_complete(future)运行直到将来(一个实例Future)完成。

  • 如果参数是一个协程对象,则它被隐式地安排为以a运行asyncio.Task。
  • 返回Future的结果或提高其异常。

loop.run_forever()

  • 运行事件循环直到stop()被调用。
  • 如果stop()在调用之前run_forever()调用,则loop将以超时为零轮询I/O选择器一次,运行为响应I/O事件而调度的所有回调(以及已调度的事件),然后退出。
  • 如果stop()在run_forever()运行时调用,则loop将运行当前批次的回调,然后退出。请注意,在这种情况下,回调计划的新回调将不会运行; 相反,它们将在下次运行run_forever()或被 run_until_complete()调用。

loop.stop()停止事件循环。

loop.is_running()

  • True如果事件循环当前正在运行则返回。

loop.is_closed()

  • True如果事件循环已关闭则返回。

loop.close()关闭事件循环。

  • 调用此函数时,loop不能运行。任何待处理的回调都将被丢弃。
  • 此方法清除所有队列并关闭执行程序,但不等待执行程序完成。
  • 这种方法是不可逆转的。关闭事件循环后,不应调用其他方法。

在了解了上述时间循环的相关方法后,再看协程的停止。

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()  # 关闭事件循环

再启动程序后立即终止程序,运行结果如下:

[外链图片转存失败(img-k9Ve4xVJ-1562057313150)(https://mp.csdn.net/72.png)]

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

Task was destroyed but it is pending!
task: <Task pending coro=<do_some_work() done,
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,运行结果如下:

[外链图片转存失败(img-ibd5jwPi-1562057313153)(https://mp.csdn.net/73.png)]

不同线程的事件循环

获取事件循环、设置或创建事件循环:

asyncio.get_running_loop()返回当前 OS 线程中正在运行的事件循环。

  • 如果没有正在运行的事件循环则会引发 RuntimeError。 此函数只能由协程或回调来调用。

asyncio.get_event_loop()获取当前事件循环。

  • 如果当前 OS 线程没有设置当前事件循环并且 set_event_loop() 还没有被调用,asyncio 将创建一个新的事件循环并将其设置为当前循环。
    使用该方法,手动创建一个事件循环,需要手动关闭。推荐使用高级API:asyncio.run()自动创建一个事件循环并在运行完毕后自动关闭。

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 )将协程提交给给定的事件循环。线程安全的。异步执行。

  • 返回a concurrent.futures.Future以等待来自另一个OS线程的结果。

此函数旨在从与运行事件循环的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-------")

运行结果如下:
[外链图片转存失败(img-t3zpF3zT-1562057313154)(https://mp.csdn.net/74.png)]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值