亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Python?asyncio的示例分析

發布時間:2021-12-13 13:35:32 來源:億速云 閱讀:197 作者:柒染 欄目:開發技術

Python asyncio的示例分析,針對這個問題,這篇文章詳細介紹了相對應的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。

我們先從一個常見的Python編程錯誤開始說起,我已經見過非常多的程序員犯過這種錯誤了:

def do_not_raise(user_defined_logic):
    try:
        user_defined_logic()
    except:
        logger.warning("User defined logic raises an exception", exc_info=True)
        # ignore

這段代碼的錯誤之處在哪里呢?

我們從Python的異常結構開始說起。Python中的異常基類有兩個,最基礎的是BaseException,第二個是Exception(繼承BaseException)。這兩者有什么區別呢?

Exception代表大部分我們經常會在業務邏輯中處理到的異常,也包括一部分運行出錯例如NameErrorAttributeError等等。但是并不是所有的異常都是Exception類的子類,少數幾個異常是繼承于BaseException的:

  • GeneratorExit

  • SystemExit

  • KeyboardInterrupt

第一個代表生成器被close()方法關閉,第二個代表系統退出(例如使用sys.exit),第三個代表程序被Ctrl+C中斷。之所以它們并不繼承于Exception,是因為:它們一般情況下絕不應當被捕獲,或者被捕獲之后應當立即reraise(通過不帶參數的raise語句)。

如果寫出上面那樣的語句,就可能會出現程序無法退出的情況:從外部發送SIGTERM信號到程序,觸發了SystemExit,然而SystemExit被捕獲然后忽略了,這樣程序就沒有正常退出,而是繼續執行下去。像SystemExit、KeyboardInterrupt、GeneratorExit這樣的異常,因為沒有固定的拋出位置,所以如果亂捕獲的話非常危險,很可能產生隱含的bug,而且測試中會很難發現。這就是為什么Python官方文檔上會強調,如果使用無參數的except,一定要配合raise重新將異常拋出。而正確的忽略執行異常的方法應該是:

def do_not_raise(user_defined_logic):
   try:
       user_defined_logic()
   except Exception:          ### <= Notice here ###
       logger.warning("User defined logic raises an exception", exc_info=True)
       # ignore

那么說了這么多,跟asyncio有什么聯系呢?

asyncio當中,一個異步過程可以通過asyncio.Task作為一個獨立執行的單元啟動,這個Task對象有一個cancel()方法,可以將它從中途強制停止。類似的,異步生成器也可以通過aclose()方法強制結束。當一個異步過程或者異步生成器被從外部強制中止的時候,會從當前的await或者yield語句拋出asyncio.CancelledError

問題就出在這個CancelledError上!

asyncio也許是為了偷懶,也許是為了和concurrent一致,這個異常實際上是concurrent.futures.CancelledError。它的基類是Exception,而不是BaseException。要知道,在concurrent庫當中,CancelledError是不會拋到已經開始了的子過程中的,它只會從future對象里拋出;而asyncio中,當使用了cancel()方法的時候,這個異常會從Task的當前堆棧位置拋出來。

這個事情就尷尬了,如果前面的do_not_raise是個異步方法,用 except Exception來捕獲了用戶自定義方法中的異常,那CancelledError也會被捕獲到。結果就是CancelledError被錯誤地忽略掉,導致cancel()方法沒有成功終止掉一個Task。

更尷尬的事情在于這個CancelledError的拋出機制。asyncio內部使用了Python的生成器和yield from機制,yield from可以自動代理異常,

為了說明這一點我們考慮下面的代碼:

import traceback
import asyncio

async def func1():
    try:
        return await func2()
    except Exception:
        traceback.print_exc()
        raise

async def func2():
    try:
        await asyncio.sleep(2)
    except Exception:
        traceback.print_exc()
        raise

async def func3():
    t1 = asyncio.ensure_future(func1())
    await asyncio.sleep(1)
    t1.cancel()
    try:
        await t1
    except CancelledError:
        pass

t1.cancel()這里,會發生什么呢?實際上異常會從最內層的func2開始拋出,從func2拋出到func1,再到func3的await t1,所以可以看到兩次traceback打印。

這就是異步方法中await的異常代理機制,它像同步調用一樣,有完整的堆棧,并且異常從最內層拋出。這本身是一個很好的設計,很方便調試,但是一旦CancelledError拋出,你是無法確定它具體從哪條語句拋出的,這樣在寫異步邏輯的時候,實際上必須假設所有的await語句都有可能拋出CancelledError。如果在外面加上了前面的do_not_raise這樣的機制,就會錯誤地忽略掉CancelledError

所以異步邏輯中的忽略異常必須寫成:

async def do_not_raise(user_defined_coroutine):
    try:
        await user_defined_coroutine
    except CancelledError:
        raise
    except Exception:
        logger.warning("User defined logic raises an exception", exc_info=True)
        # ignore

這樣才能保證CancelledError不被錯誤捕獲。

從這個結果上來看,CancelledError從一開始就不應該繼承自Exception,它應該是一個BaseException,這樣就可以減少很多異步編程中的錯誤。

并不是自己不調用cancel()就不會出現這樣的問題。一些會觸發cancel()過程的常見例子包括:

asyncio.wait_for在執行超時的時候會自動cancel內部的過程,這是一個很常用的實現超時邏輯的方法
aiohttp的handler,如果沒有處理完成之前用戶就關閉了HTTP連接(比如強制點了瀏覽器的停止按鈕),會對handler的異步過程調用cancel()
……
還有更尷尬的事情,許多時候我們不得不捕獲CancelledError。剛才的一段代碼,我故意沒有提,讀者們是否發現問題了呢?

t1.cancel()
    try:
        await t1
    except CancelledError:
        pass

在asyncio中,cancel()方法并不會立即結束一個異步Task,它只會拋出CancelledError,但是異步過程有機會使用except或者finally,在退出之前執行一些清理過程。這里的await的本意也是等待t1完全退出再繼續。但是t1會拋出CancelledError,所以捕獲這個異常,不讓它再拋出。(而且如果不這么做,asyncio會打印一行warning,表示一個異步Task失敗沒有被處理)

那么問題就來了:如果func3()在執行到這里的時候,又被外部代碼cancel()了呢?下面的except CancelledError就會變成問題,它會錯誤捕獲外部的CancelledError。另外,t1也會再次被cancel一遍(沒錯,await一個Task的時候,如果await所在過程被cancel,Task也會被cancel,需要使用asyncio.shield來規避)

正確的寫法應該是:

t1.cancel()
    await asyncio.wait([t1])
    try:
        await t1
    except CancelledError:
        pass

asyncio.wait等待Task執行結束,但并不收集結果,因此內層的CancelledError不會在這里拋出來,而且如果此時取消func3,CancelledError并不會被忽略。第二個await t1時,t1可以保證已經結束,這里內部沒有其他異步等待過程,因此CancelledError不會拋出在這里。也可以用t1.exception()之類代替。

關于Python asyncio的示例分析問題的解答就分享到這里了,希望以上內容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關注億速云行業資訊頻道了解更多相關知識。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

四会市| 大安市| 郎溪县| 石屏县| 南城县| 南京市| 讷河市| 凉城县| 达尔| 易门县| 登封市| 安图县| 景宁| 封丘县| 克什克腾旗| 民县| 承德市| 大渡口区| 孝感市| 左贡县| 汾西县| 五华县| 犍为县| 英吉沙县| 徐汇区| 长沙市| 禄丰县| 蒲城县| 于田县| 乌苏市| 乌审旗| 神池县| 苍南县| 木兰县| 探索| 云林县| 黎城县| 兴海县| 龙里县| 上蔡县| 芮城县|