python---await/async關鍵字


推文:玩轉 Python 3.5 的 await/async

首先看正常的兩個函數之間的執行

def func1():
    print("func1 start")
    print("func1 end")

def func2():
    print("func2 start")
    print("func2 a")
    print("func2 b")
    print("func2 c")
    print("func2 end")

func1()
func2()
func1 start
func1 end
func2 start
func2 a
func2 b
func2 c
func2 end

無法實現兩個函數之間的交互。

將這兩個函數設置為協程,加上async關鍵字

async def func1():
    print("func1 start")
    print("func1 end")

async def func2():
    print("func2 start")
    print("func2 a")
    print("func2 b")
    print("func2 c")
    print("func2 end")

f1 = func1()
f2 = func2()
print(f1,f2)
<coroutine object func1 at 0x000000000084C468> <coroutine object func2 at 0x000000000084CD58>
sys:1: RuntimeWarning: coroutine 'func1' was never awaited  #警告信息
sys:1: RuntimeWarning: coroutine 'func2' was never awaited

#上面不再只是函數,而變為協程對象,協程在調用時,不會被執行

那么,為什么要有一個協程對象?代碼到底如何執行?

關鍵之處是協程確實是與 Python 的生成器非常相似,也都有一個 send 方法。我們可以通過調用 send 方法來啟動一個協程的執行。

f1 = func1()
f2 = func2()

try:
    print('f1.send')
    f1.send(None)
except StopIteration as e:  #這里也是需要去捕獲StopIteration方法
    pass

try:
    print('f1.send')
    f2.send(None)
except StopIteration as e:
    pass

StopIteration 異常是一種標記生成器(或者像這里的協程)執行結束的機制。雖然這是一個異常,但是確實是我們期望的!我們可以用適當的 try-catch 代碼將其包起來,這樣就可以避免錯誤提示。

上面雖然將兩個協程都執行起來了,但是並沒有做到交替執行,和最初的並無不同,而且似乎更加麻煩

協程與線程相似的地方是多個線程之間也可以交替執行,不過與線程不同之處在於協程之間的切換是顯式的,而線程是隱式的(大多數情況下是更好的方式)。所以我們需要加入顯式切換的代碼。

我們可能根據生成器或者協程gevent的使用,而去推測這種方案:(是錯誤的

async def func1():
    print("func1 start")
    yield 
    print("func1 end")

不允許在本地協程函數中使用yield,但是作為替換,我們可以使用到await 表達式來暫停協程的執行。注意await _something_,這里的_something_代表的是基於生成器的協程對象,或者一個特殊的類似 Future 的對象。

下面使用一個基於生成器的協程函數

import types

@types.coroutine
def switch():
    yield

async def func1():
    print("func1 start")
    await switch()
    print("func1 end")

async def func2():
    print("func2 start")
    print("func2 a")
    print("func2 b")
    print("func2 c")
    print("func2 end")

f1 = func1()
f2 = func2()

try:
    f1.send(None)
except StopIteration as e:
    pass

try:
    f2.send(None)
except StopIteration as e:
    pass
func1 start
func2 start
func2 a
func2 b
func2 c
func2 end

注意上面的輸出:只是執行了函數的轉換,有func1轉換到func2,並沒有再回到func1中。那么如何讓其在回到func1中呢。

我們可以基於生成器一樣再次對func1協程使用send方法,再次激活yield向下執行

import types

@types.coroutine
def switch():
    yield

async def func1():
    print("func1 start")
    await switch()
    print("func1 end")

async def func2():
    print("func2 start")
    print("func2 a")
    print("func2 b")
    print("func2 c")
    print("func2 end")

f1 = func1()
f2 = func2()

try:
    f1.send(None)
except StopIteration as e:
    pass

try:
    f2.send(None)
except StopIteration as e:
    pass

try:
    f1.send(None)
except StopIteration as e:
    pass
func1 start
func2 start
func2 a
func2 b
func2 c
func2 end
func1 end

上面可以實現兩個函數交替一次,回到原協程中。但是我們更加希望連續不斷的調用send驅動不同的協程去執行,直到send拋出 StopIteration 異常。

為此我們

新建一個函數,這個函數傳入一個協程列表,函數執行這些協程直到全部結束。我們現在要做的就是調用這個函數。

import types

@types.coroutine
def switch():
    yield

async def func1():
    print("func1 start")
    await switch()
    print("func1 e")
    await switch()
    print("func1 end")

async def func2():
    print("func2 start")
    print("func2 a")
    await switch()
    print("func2 b")
    print("func2 c")
    await switch()
    print("func2 end")

def run(task_list):
    coro_list = list(task_list)

    while coro_list:
        for coro in list(coro_list):
            try:
                coro.send(None)
            except StopIteration:
                coro_list.remove(coro)

f1 = func1()
f2 = func2()

run([f1,f2])

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM