深刻Asyncio(三)Asyncio初體驗

Asyncio初體驗

Asyncio在Python中提供的API很複雜,其旨在替不一樣羣體的人解決不一樣的問題,也正是因爲這個緣由,因此很難區分重點。python

能夠根據asyncio在Python中的特性,將其劃分爲兩大主要羣體:
1. 應用(最終用戶)開發者,想要在應用開發中使用asyncio;
2. 框架開發者,製做框架或庫以供應用開發者在他們的開發中使用。編程

在asyncio社區中大部分的問題基本都與這兩個部分相關,例如,asyncio的官方文檔更像是給框架開發者使用的,而非應用開發者,這致使應用開發者在閱讀文檔時很容易被其複雜性所震撼,給讀者一種錯覺就是在使用它以前,得看徹底部的文檔。bash

QuickStart

不須要關心官方文檔的內容,要掌握asyncio庫比想象中要容易。網絡

PEP 492的做者、async Python的主要貢獻者——Yury Selivanov——曾說過,asyncio的不少API都是給框架開發者提供的,應用開發人員須要掌握的只是全部API中的一小部分。框架

在本節咱們將研究這些核心特性,並瞭解如何在Python中使用基於事件loop的編程,以此實現基本的異步。異步

要成爲一個掌握asyncio的應用開發者,你須要知道的東西其實能夠用一個小例子來展現。socket

import time
import asyncio


async def main():
    print(f'{time.ctime()} Hello')
    await asyncio.sleep(1.0)
    print(f'{time.ctime()} Goodbye')
    loop.stop()    # 1


loop = asyncio.get_event_loop()    # 2
loop.create_task(main())    # 3
loop.run_forever()    # 4
pending = asyncio.Task.all_tasks(loop=loop)
group = asyncio.gather(*pending, return_exceptions=True)    # 5
loop.run_until_complete(group)    # 6
loop.close()    # 7
λ  python quickstart.py
Fri Sep 28 19:39:33 2018 Hello
Fri Sep 28 19:39:34 2018 Goodbye
  1. 一般用於信號控制,暫停循環,但循環能夠從新啓用不會消失;
  2. 在運行協程以前得到一個循環實例,只要是在單線程中,這個實例就是單例的;
  3. 只有調用這個方法,協程才能被執行,該調用返回的task對象能夠用於獲取任務狀態、結果,或可經過task.cancel()取消任務;
  4. 實現循環運行的方法之一,會阻塞當前線程(一般是主線程);
  5. 一般習慣是在程序入口處執行loop.run_forever()方法,在收到中止信號時中止循環,而後收集那些還未完成的task,調用loop.run_until_complete()方法等待其執行完畢,但更多的是用這個方法收集協程任務並取消它們,而後等待其執行完畢;
  6. 實現循環運行的方法之一,一樣阻塞當前線程,其保持循環運行直到其上調度的協程完成;
  7. 一般在最後調用,必須在調用loop.stop()方法的基礎上使用,會致使循環永久消失。

上述例子漏了一些東西,其中最重要的是如何運行阻塞函數,咱們知道協程就是函數中使用了await關鍵字進行切換,但在當前async def還沒得到普遍支持前,使用阻塞函數/庫是不可避免的。async

爲此,asyncio提供了一個與concurrent.futures包中的API相似的API,它提供了ThreadPoolExecutorProcessPoolExecutor,默認基於線程,但很容易用基於進程的替換,這裏面有些特殊的地方要注意。函數

import time
import asyncio


async def main():
    print(f'{time.ctime()} Hello')
    await asyncio.sleep(1.0)
    print(f'{time.ctime()} Goodbye')
    loop.stop()


def blocking(): # 1
    time.sleep(0.5) # 2
    print(f'{time.ctime()} Hello from a thread!')


loop = asyncio.get_event_loop()
loop.create_task(main())
loop.run_in_executor(None, blocking)    # 3

loop.run_forever()
pending = asyncio.Task.all_tasks(loop=loop) # 4
group = asyncio.gather(*pending)
loop.run_until_complete(group)
loop.close()
λ  python quickstart_exe.py
Fri Sep 28 20:21:21 2018 Hello
Fri Sep 28 20:21:22 2018 Hello from a thread!
Fri Sep 28 20:21:22 2018 Goodbye
  1. 這個函數調用了常規的sleep(),這會阻塞主線程並阻止loop運行,咱們不能使這個函數變成協程,更糟糕的是不能在主線程運行loop時調用它,解決辦法是用一個executor來運行它;
  2. 注意一點,這個sleep運行時間比協程中的sleep運行時間要短,後文再討論若是長的話會發生什麼;
  3. 該方法幫助咱們在事件loop裏用額外的線程或進程執行函數,這個方法的返回值是一個Future對象,意味着能夠用await來切換它;
  4. 掛起的task中不包含前面的阻塞函數,而且這個方法只返回task對象,絕對不會返回Future對象。

好了,經過上面的學習,已經掌握了應用開發者對於asyncio庫須要的最重要的部分,接下來將拓展知識並對API進行層次理解,這會讓你更容易理解如何從文檔中獲取信息。工具

Asyncio全覽

從前面一節發現,應用開發者只需幾個命令就可使用asyncio,但不幸的是官方文檔巨量的API和扁平化的顯示格式,讓人很難分清哪些命令更通用,哪些命令是向框架開發者提供的,框架開發者能夠經過文檔尋找鉤子並鏈接到其框架中,接下來咱們從框架開發者的角度來看他們如何構建新的異步兼容庫。

Level Concept Implementation
9 Network: streams StreamReader & StreamWriter
8 Network: TCP & UDP Protocol
7 Network: transports BaseTransport
6 tools asyncio.Queue
5 subprocesses & threads run_in_executor(), asyncio.subprocess
4 tasks asyncio.Task
3 futures asyncio.Future
2 event loop BaseEventLoop
1 coroutines async def & await

上表中加粗字體對於應用開發者最重要,級別分爲9級,1級最基礎。

  1. 一級,設計第三方框架的最低級別,但在流行的CurioTrio中並不經常使用,它們只依賴於Python中的本地協程,不依賴asyncio庫模塊;
  2. 二級,事件loop被分離出來,所以能夠對其進行替換,好比uvloop實現了比標準庫更快的循環;
  3. 三四級,帶來了Future和Task對象,Task是Future的子類,可將它們簡單地視做同一個等級,Future對象表示某種正在進行的動做,其將經過事件循環的notification返回結果,而Task對象表示運行在事件循環上的協程,簡單理解爲Future是「循環感知」的,Task是「循環感知」+「異步感知」的,應用開發更多地使用Task,框架開發者的使用比例要看代碼細節;
  4. 五級,有關啓動方面工具,並等待運行在單獨的線程或進程上的工做;
  5. 六級,附加的異步通訊工具,好比asyncio.Queue,提供與Queue模塊相似的API,但原版的get和put方法會阻塞線程,所以這裏提供的隊列經過增長wait關鍵字以支持異步;
  6. 七到九級,網絡IO層級,對應用開發者來講Stream十分方便,Protocol提供比Stream更細粒度的API,全部能使用Stream的地方都能用Protocol代替,除非要建立一個定製傳輸協議的框架供他人使用,不然幾乎用不到Transport。

小結

在QuickStart中,掌握了快速上手的幾個API;如今,對整個asyncio有了清晰的層級劃分。這裏再對上述知識進行強調:

  1. 第一級,知道如何寫async def函數,如何使用await關鍵字來調用執行其它協程;
  2. 第二級,知道如何進行事件loop開關、交互;
  3. 第五級,知道如何在事件loop中運行阻塞程序,由於大多數第三方庫都不支持異步,好比ORM庫;
  4. 第六級,協程間數據通訊使用asyncio.Queue等;
  5. 第九級,Streams的API提供了最簡單的方式來使用socket進行網絡通訊。

若是使用提供異步兼容的第三方庫,如aiohttp,那麼就不用直接使用asyncio的網絡層,但這會致使對第三方庫的依賴。

隨着Python的發展,asyncio庫之後可能會提供更多的API,如今也只是大體地分了幾個層級。

相關文章
相關標籤/搜索