本節將的是loop基本用法,其實在一般情況下是不會對loop進行操作,而對於框架的搭建者,要更精細的控制loop 是需要對loop進行更進一步的理解的。
事件循環是每個 asyncio 應用的核心。 事件循環會運行異步任務和回調,執行網絡 IO 操作,以及運行子進程。
loop.call_soon(callback, *args, context=None)
安排在下一次事件循環的迭代中使用 args 參數調用 callback 。
回調按其註冊順序被調用。每個回調僅被調用一次。
返回一個能用來取消回調的 asyncio.Handle 實例。
loop.call_soon
協程一運行就馬上運行,根據註冊的順序執行
import asyncio
import time
def hello(name): # 普通函數
print('[hello] Hello, {}'.format(name))
async def work(t, name): # 協程函數
print('[work] start', name)
await asyncio.sleep(t)
print('[work] {} after {}s stop'.format(name, t))
def main():
loop = asyncio.get_event_loop() # 創建事件循環
asyncio.ensure_future(work(1, 'A')) # 向事件循環中添加任務(第 1 個執行
# call_soon 將普通函數當作 task 加入到事件循環並排定執行順序
# 該方法的第一個參數爲普通函數名字,普通函數的參數寫在後面 (第 2 個執行
loop.call_soon(hello, 'Tom')
loop.create_task(work(2, 'B')) # 向事件循環中添加任務(第 3 個執行
# 阻塞啓動事件循環,順便再添加一個任務 (第 4 個執行
loop.run_until_complete(work(3, 'C'))
if __name__ == '__main__':
main()
loop.stop
此方法停止事件循環之前,每個任務都會獲得一次執行機會,得到執行的結果
import asyncio
import time
def hello(name): # 普通函數
print('[hello] Hello, {}'.format(name))
def stop(loop): # 普通函數
# 事件循環的 stop 方法會停止全部任務後結束事件循環
# 在停止前,每個任務都將運行一次,直到遇到 IO 阻塞
# 可以嘗試將下面這行代碼放到任意普通函數和協程函數的任意位置執行來查看區別
loop.stop()
async def work(t, name): # 協程函數
print('[work] start', name)
await asyncio.sleep(t)
print('[work] {} after {}s stop'.format(name, t))
def main():
loop = asyncio.get_event_loop()
asyncio.ensure_future(work(1, 'A')) # 第 1 個執行
loop.call_soon(hello, 'Tom') # 第 2 個執行
# 這個任務雖然是第 3 個執行,但要等待每個任務執行一次之後纔會 stop
loop.call_soon(stop, loop) # 第 3 個執行
loop.create_task(work(2, 'B')) # 第 4 個執行
# 爲了讓 run.stop 方法起作用
# 這裏使用 run_forever 方法無限運行事件循環,直到 run.stop 執行
loop.run_forever()
if __name__ == '__main__':
main()
loop.call_later(delay, callback, *args, context=None)
此方法同 call_soon 一樣,可將普通函數作爲任務放到事件循環裏,不同之處在於此方法可延時執行,第一個參數爲延時時間
delay的時間計算是從loop循環開始
import asyncio
import functools
def hello(name): # 普通函數
print('[hello] Hello, {}'.format(name))
async def work(t, name): # 協程函數
print('[work{}] start'.format(name))
await asyncio.sleep(t)
print('[work{}] stop'.format(name))
def main():
loop = asyncio.get_event_loop()
asyncio.ensure_future(work(1, 'A')) # 任務 1
loop.call_later(1.2, hello, 'Tom') # 任務 2
loop.call_soon(hello, 'Kitty') # 任務 3
task4 = loop.create_task(work(2, 'B')) # 任務 4
loop.call_later(1, hello, 'Jerry') # 任務 5
# 這個方法的參數其實無所謂,有就行,但參數須爲 future / task
loop.run_until_complete(task4)
if __name__ == '__main__':
# 毫無疑問,這五個任務在一個事件循環裏是順序執行,遇到阻塞執行下一個
# 首先執行任務一,打印一行後阻塞 1 秒,執行任務二
# 任務二是 call_later 1.2 秒,就相當於一個 1.2 秒的 asyncio.sleep
# 注意,call_later 這個延時 1.2 秒是事件循環啓動時就開始計時的
# 任務二阻塞,執行任務三,這個簡單,打印一行就完事兒
# 接着執行任務四,打印一行後阻塞 2 秒
# 接着執行任務五,還是 call_later 1 秒,阻塞
# 以上是五個任務第一輪的執行情況
# 第二輪開始前,CPU 一直候着,現在還有 4 個任務,任務三已完成
# 第一個發出執行信號的是任務五,它只阻塞 1 秒
# 上面已經說了,這個 1 秒是從事件循環啓動時開始算
# 所以這個阻塞肯定比任務一的阻塞 1 秒先結束
# CPU 執行完任務五,任務一也阻塞結束了,執行之
# 然後是任務二,最後是任務四
# 第二輪打印了 4 行,全部任務完成,停止事件循環
main()
運行結果:
[workA] start
[hello] Hello, Kitty
[workB] start
[hello] Hello, Jerry
[workA] stop
[hello] Hello, Tom
[workB] stop
loop.call_at & loop.time
call_soon 立刻執行,call_later 延時執行,call_at 在某時刻執行。loop.time 就是事件循環內部的一個計時方法,返回值是時刻,數據類型是 float
import asyncio
import functools
def hello(name): # 普通函數
print('[hello] Hello, {}'.format(name))
async def work(t, name): # 協程函數
print('[work{}] start'.format(name))
await asyncio.sleep(t)
print('[work{}] stop'.format(name))
def main():
loop = asyncio.get_event_loop()
start = loop.time() # 事件循環內部時刻
asyncio.ensure_future(work(1, 'A')) # 任務 1
# loop.call_later(1.2, hello, 'Tom')
# 上面註釋這行等同於下面這行
loop.call_at(start + 1.2, hello, 'Tom') # 任務 2
loop.call_soon(hello, 'Kitty') # 任務 3
task4 = loop.create_task(work(2, 'B')) # 任務 4
# loop.call_later(1, hello, 'Jerry')
# 上面註釋這行等同於下面這行
loop.call_at(start + 1, hello, 'Jerry') # 任務 5
# 這個方法的參數其實無所謂,有就行,但參數須爲 future / task
loop.run_until_complete(task4)
if __name__ == '__main__':
main()
運行結果和上面的相同
asyncio.lock 協程鎖
協程鎖通常使用在子協程中,其作用是將協程內部的一段代碼鎖住,直到這段代碼運行完畢解鎖。協程鎖的固定用法是使用 async with 創建協程鎖的上下文環境,將代碼塊寫入其中,示例如下:
import asyncio
l = []
lock = asyncio.Lock() # 協程鎖
async def work(name):
print('lalalalalalalala') # 打印此信息是爲了測試協程鎖的控制範圍
# 這塊兒加個鎖,第一次調用該協程,運行到這個語句塊,上鎖
# 當語句塊結束後解鎖,開鎖前該語句塊不可被運行第二次
# 如果上鎖後有其它任務調用了這個協程函數,運行到這步會被阻塞,直至解鎖
# with 是普通上下文管理器關鍵字,async with 是異步上下文管理器關鍵字
# 能夠使用 with 關鍵字的對象須有 __enter__ 和 __exit__ 方法
# 能夠使用 async with 關鍵字的對象須有 __aenter__ 和 __aexit__ 方法
# async with 會自動運行 lock 的 __aenter__ 方法,該方法會調用 acquire 方法上鎖
# 在語句塊結束時自動運行 __aexit__ 方法,該方法會調用 release 方法解鎖
# 這和 with 一樣,都是簡化 try ... finally 語句
async with lock:
print('{} start'.format(name)) # 頭一次運行該協程時打印
if 'x' in l: # 如果判斷成功
return name # 直接返回結束協程,不再向下執行
await asyncio.sleep(1)
print('----------',name) # 阻塞 0 秒,切換協程
l.append('x')
print('{} end'.format(name))
return name
async def one():
name = await work('one')
print('{} ok'.format(name))
async def two():
name = await work('two')
print('{} ok'.format(name))
def main():
loop = asyncio.get_event_loop()
tasks = asyncio.wait([one(), two()])
loop.run_until_complete(tasks)
if __name__ == '__main__':
main()
運行結果:
lalalalalalalala
one start
lalalalalalalala
---------- one
one end
one ok
two start
two ok