中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

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

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

Python混合怎么使用同步和異步函數

發布時間:2023-05-10 16:13:48 來源:億速云 閱讀:117 作者:iii 欄目:開發技術

本篇內容主要講解“Python混合怎么使用同步和異步函數”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“Python混合怎么使用同步和異步函數”吧!

    在協程函數中調用同步函數

    在協程函數中直接調用同步函數會阻塞事件循環,從而影響整個程序的性能。我們先來看一個例子:

    以下是使用異步 Web 框架 FastAPI 寫的一個例子,FastAPI 是比較快,但不正確的操作將會變得很慢。

    import time
    
    from fastapi import FastAPI
    
    app = FastAPI()
    
    
    @app.get("/")
    async def root():
        time.sleep(10)
        return {"message": "Hello World"}
    
    
    @app.get("/health")
    async def health():
        return {"status": "ok"}

    上面我們寫了兩個接口,假設 root 接口函數耗時 10 秒,在這 10 秒內訪問 health 接口,想一想會發生什么?

    Python混合怎么使用同步和異步函數

    訪問 root 接口(左),立即訪問 health 接口(右),health 接口被阻塞,直至 root 接口返回后,health 接口才成功響應。

    time.sleep 就是一個「同步」函數,它會阻塞整個事件循環。

    如何解決呢?想一想以前的處理方法,如果一個函數會阻塞主線程,那么就再開一個線程讓這個阻塞函數單獨運行。所以,這里也是同理,開一個線程單獨去運行那些阻塞式操作,比如讀取文件等。

    loop.run_in_executor 方法將同步函數轉換為異步非阻塞方式進行處理。具體來說,loop.run_in_executor() 可以將同步函數創建為一個線程進程,并在其中執行該函數,從而避免阻塞事件循環。

    官方例子:在線程或者進程池中執行代碼。

    那么,我們使用 loop.run_in_executor 改寫上面例子,如下:

    import asyncio
    import time
    
    from fastapi import FastAPI
    
    app = FastAPI()
    
    
    @app.get("/")
    async def root():
        loop = asyncio.get_event_loop()
    
        def do_blocking_work():
            time.sleep(10)
            print("Done blocking work!!")
    
        await loop.run_in_executor(None, do_blocking_work)
        return {"message": "Hello World"}
    
    
    @app.get("/health")
    async def health():
        return {"status": "ok"}

    效果如下:

    Python混合怎么使用同步和異步函數

    root 接口被阻塞期間,health 依然正常訪問互不影響。

    注意: 這里都是為了演示,實際在使用 FastAPI 開發時,你可以直接將 async def root 更換成 def root ,也就是將其換成同步接口函數,FastAPI 內部會自動創建線程處理這個同步接口函數。總的來說,FastAPI 內部也是依靠線程去處理同步函數從而避免阻塞主線程(或主線程中的事件循環)。

    在同步函數中調用異步函數

    協程只能在「事件循環」內被執行,且同一時刻只能有一個協程被執行。

    所以,在同步函數中調用異步函數,其本質就是將協程「扔進」事件循環中,等待該協程執行完獲取結果即可。

    以下這些函數,都可以實現這個效果:

    • asyncio.run

    • asyncio.run_coroutine_threadsafe

    • loop.run_until_complete

    • create_task

    接下來,我們將一一講解這些方法并舉例說明。

    asyncio.run

    這個方法使用起來最簡單,先看下如何使用,然后緊跟著講一下哪些場景不能直接使用 asyncio.run

    import asyncio
    
    async def do_work():
        return 1
    
    def main():
        result = asyncio.run(do_work())
        print(result)  # 1
    
    if __name__ == "__main__":
        main()

    直接 run 就完事了,然后接受返回值即可。

    但是需要,注意的是 asyncio.run 每次調用都會新開一個事件循環,當結束時自動關閉該事件循環。

    一個線程內只存在一個事件循環,所以如果當前線程已經有存在的事件循環了,就不應該使用 asyncio.run 了,否則就會拋出如下異常:

    RuntimeError: asyncio.run() cannot be called from a running event loop

    因此,asyncio.run 用作新開一個事件循環時使用。

    asyncio.run_coroutine_threadsafe

    文檔: https://docs.python.org/zh-cn/3/library/asyncio-task.html#asyncio.run_coroutine_threadsafe

    向指定事件循環提交一個協程。(線程安全)
    返回一個 concurrent.futures.Future 以等待來自其他 OS 線程的結果。

    換句話說,就是將協程丟給其他線程中的事件循環去運行

    值得注意的是這里的「事件循環」應該是其他線程中的事件循環,非當前線程的事件循環。

    其返回的結果是一個 future 對象,如果你需要獲取協程的執行結果可以使用 future.result() 獲取,關于 future 對象的更多介紹,見 https://docs.python.org/zh-cn/3/library/concurrent.futures.html#concurrent.futures.Future

    下方給了一個例子,一共有兩個線程:thread_with_loopanother_thread,分別用于啟動事件循環和調用 run_coroutine_threadsafe

    import asyncio
    import threading
    import time
    
    loop = None
    
    
    def get_loop():
        global loop
        if loop is None:
            loop = asyncio.new_event_loop()
        return loop
    
    
    def another_thread():
        async def coro_func():
            return 1
    
        loop = get_loop()
        # 將協程提交到另一個線程的事件循環中執行
        future = asyncio.run_coroutine_threadsafe(coro_func(), loop)
        # 等待協程執行結果
        print(future.result())
        # 停止事件循環
        loop.call_soon_threadsafe(loop.stop)
    
    
    def thread_with_loop():
        loop = get_loop()
        # 啟動事件循環,確保事件循環不會退出,直到 loop.stop() 被調用
        loop.run_forever()
        loop.close()
    
    
    # 啟動一個線程,線程內部啟動了一個事件循環
    threading.Thread(target=thread_with_loop).start()
    time.sleep(1)
    # 在主線程中啟動一個協程, 并將協程提交到另一個線程的事件循環中執行
    t = threading.Thread(target=another_thread)
    t.start()
    t.join()

    loop.run_until_complete

    文檔: https://docs.python.org/zh-cn/3.10/library/asyncio-eventloop.html#asyncio.loop.run_until_complete

    運行直到 future ( Future 的實例 ) 被完成。

    這個方法和 asyncio.run 類似。

    具體就是傳入一個協程對象或者任務,然后可以直接拿到協程的返回值。

    run_until_complete 屬于 loop 對象的方法,所以這個方法的使用前提是有一個事件循環,注意這個事件循環必須是非運行狀態,如果是運行中就會拋出如下異常:

    RuntimeError: This event loop is already running

    例子:

    loop = asyncio.new_event_loop()
    loop.run_until_complete(do_async_work())

    create_task

    文檔: https://docs.python.org/zh-cn/3/library/asyncio-task.html#creating-tasks

    再次準確一點:要運行一個協程函數的本質是將攜帶協程函數的任務提交至事件循環中,由事件循環發現、調度并執行。

    其實一共就是滿足兩個條件:

    • 任務;

    • 事件循環。

    我們使用 async def func 定義的函數叫做協程函數func() 這樣調用之后返回的結果是協程對象,到這一步協程函數內的代碼都沒有被執行,直到協程對象被包裝成了任務,事件循環才會“正眼看它們”。

    所以事件循環調度運行的基本單元就是任務,那為什么我們在使用 async/await 這些語句時沒有涉及到任務這個概念呢?

    這是因為 await 語法糖在內部將協程對象封裝成了任務,再次強調事件循環只認識任務

    所以,想要運行一個協程對象,其實就是將協程對象封裝成一個任務,至于事件循環是如何發現、調度和執行的,這個我們不用關心。

    那將協程封裝成的任務的方法有哪些呢?

    • asyncio.create_task

    • asyncio.ensure_future

    • loop.create_task

    看著有好幾個的,沒關系,我們只關心 loop.create_task,因為其他方法最終都是調用 loop.create_task

    使用起來也是很簡單的,將協程對象傳入,返回值是一個任務對象。

    async def do_work():
        return 222
    
    task = loop.create_task(do_work())

    do_work 會被異步執行,那么 do_work 的結果怎么獲取呢,task.result() 可以嗎?

    分情況:

    • 如果是在一個協程函數內使用 await task.result(),這是可以的;

    • 如果是在普通函數內則不行。你不可能立即獲得協程函數的返回值,因為協程函數還沒有被執行呢。

    asyncio.Task 運行使用 add_done_callback 添加完成時的回調函數,所以我們可以「曲線救國」,使用回調函數將結果添加到隊列、Future 等等。

    我這里給個基于 concurrent.futures.Future 獲取結果的例子,如下:

    import asyncio
    from asyncio import Task
    from concurrent.futures import Future
    
    from fastapi import FastAPI
    
    app = FastAPI()
    loop = asyncio.get_event_loop()
    
    
    async def do_work1():
        return 222
    
    
    @app.get("/")
    def root():
        # 新建一個 future 對象,用于接受結果值
        future = Future()
    
        # 提交任務至事件循環
        task = loop.create_task(do_work1())
    
        # 回調函數
        def done_callback(task: Task):
            # 設置結果
            future.set_result(task.result())
    
        # 為這個任務添加回調函數
        task.add_done_callback(done_callback)
    
        # future.result 會被阻塞,直到有結果返回為止
        return future.result()  # 222

    到此,相信大家對“Python混合怎么使用同步和異步函數”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!

    向AI問一下細節

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

    AI

    呼伦贝尔市| 湘潭市| 沾化县| 延庆县| 云阳县| 太白县| 讷河市| 靖江市| 中西区| 南康市| 寿阳县| 利辛县| 汽车| 通榆县| 巴东县| 宾川县| 铁岭县| 额尔古纳市| 五家渠市| 寿光市| 沧州市| 三穗县| 固安县| 公安县| 个旧市| 富平县| 泊头市| 交口县| 文安县| 阿拉善盟| 资源县| 衡南县| 铜梁县| 庐江县| 九江县| 兴隆县| 安徽省| 泽普县| 九龙城区| 江孜县| 繁昌县|