พูดถึงasync/await
และasyncio
ไม่ใช่เรื่องเดียวกัน อย่างแรกคือโครงสร้างพื้นฐานระดับต่ำ (โครูทีน) ในขณะที่ต่อมาคือไลบรารีที่ใช้โครงสร้างเหล่านี้ ในทางกลับกันไม่มีคำตอบสุดท้ายเดียว
ต่อไปนี้เป็นคำอธิบายทั่วไปเกี่ยวกับการทำงานของไลบรารีasync/await
และasyncio
ไลบรารี นั่นคืออาจมีเทคนิคอื่น ๆ อยู่ด้านบน (มี ... ) แต่มันไม่สำคัญเว้นแต่คุณจะสร้างขึ้นเอง ความแตกต่างควรมีเล็กน้อยเว้นแต่คุณจะรู้ดีพอที่จะไม่ต้องถามคำถามดังกล่าว
1. โครูทีนเทียบกับรูทีนย่อยในเปลือกถั่ว
เช่นเดียวกับซับรูทีน (ฟังก์ชั่นขั้นตอน ... ), coroutines (เครื่องปั่นไฟ, ... ) เป็นนามธรรมของสแต็คโทรและตัวชี้สอน: มีสแต็คของการดำเนินการชิ้นรหัสและแต่ละที่การเรียนการสอนที่เฉพาะเจาะจง
ความแตกต่างของdef
เทียบกับasync def
เป็นเพียงเพื่อความชัดเจน ความแตกต่างที่เกิดขึ้นจริงเมื่อเทียบกับreturn
yield
จากนี้await
หรือyield from
ใช้ความแตกต่างจากการโทรแต่ละครั้งไปจนถึงสแต็กทั้งหมด
1.1. รูทีนย่อย
รูทีนย่อยแสดงถึงระดับสแต็กใหม่เพื่อเก็บตัวแปรโลคัลและการส่งผ่านเพียงครั้งเดียวของคำสั่งเพื่อไปยังจุดสิ้นสุด พิจารณารูทีนย่อยดังนี้:
def subfoo(bar):
qux = 3
return qux * bar
เมื่อคุณเรียกใช้นั่นหมายความว่า
- จัดสรรพื้นที่สแต็กสำหรับ
bar
และqux
- เรียกใช้คำสั่งแรกซ้ำแล้วข้ามไปยังคำสั่งถัดไป
- ครั้งหนึ่งที่
return
ผลักดันค่าของสแต็คโทร
- ล้างสแต็ก (1. ) และตัวชี้คำสั่ง (2. )
โดยเฉพาะอย่างยิ่ง 4. หมายความว่ารูทีนย่อยเริ่มต้นที่สถานะเดียวกันเสมอ ทุกอย่างที่เป็นเอกสิทธิ์ของฟังก์ชันจะหายไปเมื่อเสร็จสิ้น ไม่สามารถเรียกใช้ฟังก์ชันต่อได้แม้ว่าจะมีคำแนะนำตามมาreturn
ก็ตาม
root -\
: \- subfoo --\
:/--<---return --/
|
V
1.2. โครูทีนเป็นรูทีนย่อยถาวร
โครูทีนเป็นเหมือนรูทีนย่อย แต่สามารถออกได้โดยไม่ทำลายสถานะ พิจารณาโครูทีนดังนี้:
def cofoo(bar):
qux = yield bar
return qux
เมื่อคุณเรียกใช้นั่นหมายความว่า
- จัดสรรพื้นที่สแต็กสำหรับ
bar
และqux
- เรียกใช้คำสั่งแรกซ้ำแล้วข้ามไปยังคำสั่งถัดไป
- ครั้งหนึ่งที่
yield
ผลักดันค่าของสแต็คโทรแต่เก็บกองและตัวชี้สอน
- เมื่อโทรเข้า
yield
ให้เรียกคืนสแต็กและตัวชี้คำสั่งและส่งอาร์กิวเมนต์ไปที่qux
- ครั้งหนึ่งที่
return
ผลักดันค่าของสแต็คโทร
- ล้างสแต็ก (1. ) และตัวชี้คำสั่ง (2. )
หมายเหตุการเพิ่มเติมของ 2.1 และ 2.2 - โครูทีนสามารถระงับและกลับมาดำเนินการต่อได้ที่จุดที่กำหนดไว้ล่วงหน้า สิ่งนี้คล้ายกับการที่รูทีนย่อยถูกระงับระหว่างการเรียกรูทีนย่อยอื่น ความแตกต่างคือโครูทีนที่ใช้งานอยู่ไม่ได้ถูกผูกไว้อย่างเคร่งครัดกับสแต็กการโทร แต่โครูทีนที่ถูกระงับจะเป็นส่วนหนึ่งของสแต็กแยกต่างหาก
root -\
: \- cofoo --\
:/--<+--yield --/
| :
V :
ซึ่งหมายความว่าโครูทีนที่ถูกระงับสามารถจัดเก็บหรือเคลื่อนย้ายระหว่างสแต็กได้อย่างอิสระ กลุ่มการโทรใด ๆ ที่สามารถเข้าถึงโครูทีนสามารถตัดสินใจที่จะดำเนินการต่อได้
1.3. ข้ามโทรสแต็ก
จนถึงตอนนี้โครูทีนของเราจะลดระดับการโทรด้วยyield
. รูทีนย่อยสามารถลงและขึ้น call stack ด้วยreturn
และ()
. เพื่อความสมบูรณ์โครูทีนจำเป็นต้องมีกลไกในการขึ้น call stack พิจารณาโครูทีนดังนี้:
def wrap():
yield 'before'
yield from cofoo()
yield 'after'
เมื่อคุณเรียกใช้นั่นหมายความว่ามันยังคงจัดสรรสแตกและตัวชี้คำสั่งเหมือนรูทีนย่อย เมื่อระงับการทำงานนั้นจะเหมือนกับการจัดเก็บรูทีนย่อย
แต่yield from
ไม่ทั้งสอง มัน suspends สแต็คและตัวชี้สอนของwrap
และcofoo
วิ่ง โปรดทราบว่าwrap
จะหยุดชั่วคราวจนกว่าจะcofoo
เสร็จสิ้นอย่างสมบูรณ์ เมื่อใดก็ตามที่cofoo
ระงับหรือมีการส่งบางสิ่งจะcofoo
เชื่อมต่อโดยตรงกับสแต็กการโทร
1.4. โครูทีนจนสุด
ตามที่กำหนดไว้yield from
อนุญาตให้เชื่อมต่อสองขอบเขตกับอีกหนึ่งขอบเขตระดับกลาง เมื่อนำไปใช้ซ้ำนั่นหมายความว่าด้านบนของสแต็กสามารถเชื่อมต่อกับด้านล่างของสแต็กได้
root -\
: \-> coro_a -yield-from-> coro_b --\
:/ <-+------------------------yield ---/
| :
:\ --+-- coro_a.send----------yield ---\
: coro_b <-/
สังเกตว่าroot
และcoro_b
ไม่รู้เกี่ยวกับกันและกัน สิ่งนี้ทำให้โครูทีนสะอาดกว่าการเรียกกลับมาก: โครูทีนยังคงสร้างขึ้นบนความสัมพันธ์ 1: 1 เหมือนรูทีนย่อย Coroutines ระงับและดำเนินการต่อการดำเนินการที่มีอยู่ทั้งหมดจนกว่าจะถึงจุดเรียกใช้งานปกติ
โดยเฉพาะอย่างยิ่งroot
อาจมีจำนวนโครูทีนเพื่อดำเนินการต่อ แต่ก็ไม่สามารถกลับมาทำงานต่อได้มากกว่าหนึ่งรายการในเวลาเดียวกัน โครูทีนของรูทเดียวกันพร้อมกัน แต่ไม่ขนานกัน!
1.5. Python async
และawait
จนถึงตอนนี้คำอธิบายได้ใช้คำศัพท์yield
และyield from
คำศัพท์ของเครื่องกำเนิดไฟฟ้าอย่างชัดเจน- ฟังก์ชันพื้นฐานเหมือนกัน ไวยากรณ์ Python3.5 ใหม่async
และawait
มีไว้เพื่อความชัดเจนเป็นหลัก
def foo():
return None
def foo():
yield from foofoo()
async def foo():
await foofoo()
return None
async for
และasync with
งบที่มีความจำเป็นเพราะคุณจะทำลายyield from/await
ห่วงโซ่กับเปลือยfor
และwith
งบ
2. กายวิภาคของห่วงเหตุการณ์อย่างง่าย
โดยตัวของมันเอง coroutine มีแนวคิดของการควบคุมร่ำ ๆ จะไม่มีอีก coroutine สามารถให้การควบคุมกับผู้โทรที่ด้านล่างของกองโคโรทีนเท่านั้น จากนั้นผู้โทรนี้สามารถเปลี่ยนไปใช้โครูทีนอื่นและเรียกใช้
โหนดรูทของโครูทีนหลายตัวมักเป็นลูปเหตุการณ์ : เมื่อระงับโครูทีนให้เหตุการณ์ที่ต้องการดำเนินการต่อ ในทางกลับกันการวนซ้ำของเหตุการณ์สามารถรอให้เหตุการณ์เหล่านี้เกิดขึ้นได้อย่างมีประสิทธิภาพ สิ่งนี้ช่วยให้สามารถตัดสินใจได้ว่าจะเรียกใช้โครูทีนใดต่อไปหรือจะรออย่างไรก่อนที่จะดำเนินการต่อ
การออกแบบดังกล่าวบ่งบอกเป็นนัยว่ามีชุดของเหตุการณ์ที่กำหนดไว้ล่วงหน้าซึ่งลูปเข้าใจ หลาย ๆ อย่างประสานawait
กันจนในที่สุดเหตุการณ์ก็เกิดawait
ขึ้น เหตุการณ์นี้สามารถสื่อสารโดยตรงกับลูปเหตุการณ์โดยyield
การควบคุม
loop -\
: \-> coroutine --await--> event --\
:/ <-+----------------------- yield --/
| :
| :
| :
:\ --+-- send(reply) -------- yield --\
: coroutine <--yield-- event <-/
กุญแจสำคัญคือการระงับโครูทีนช่วยให้ลูปเหตุการณ์และเหตุการณ์สื่อสารกันได้โดยตรง สแต็กโครูทีนระดับกลางไม่จำเป็นต้องมีความรู้ใด ๆเกี่ยวกับลูปที่กำลังรันอยู่และเหตุการณ์ต่างๆทำงานอย่างไร
2.1.1. เหตุการณ์ในช่วงเวลา
เหตุการณ์ที่ง่ายที่สุดในการจัดการคือการมาถึงจุดหนึ่ง นี่เป็นบล็อกพื้นฐานของโค้ดเธรดเช่นกัน: เธรดซ้ำsleep
s จนกว่าเงื่อนไขจะเป็นจริง อย่างไรก็ตามsleep
การดำเนินการบล็อกปกติด้วยตัวเอง - เราต้องการให้โครูทีนอื่น ๆ ไม่ถูกบล็อก แต่เราต้องการบอกเหตุการณ์ที่วนซ้ำเมื่อควรกลับมาใช้สแต็กโครูทีนปัจจุบัน
2.1.2. การกำหนดเหตุการณ์
เหตุการณ์เป็นเพียงคุณค่าที่เราระบุได้ไม่ว่าจะเป็นทาง enum ประเภทหรือข้อมูลประจำตัวอื่น ๆ เราสามารถกำหนดสิ่งนี้ได้ด้วยคลาสง่ายๆที่เก็บเวลาเป้าหมายของเรา นอกจากการจัดเก็บข้อมูลเหตุการณ์แล้วเราสามารถอนุญาตให้เข้าawait
เรียนได้โดยตรง
class AsyncSleep:
"""Event to sleep until a point in time"""
def __init__(self, until: float):
self.until = until
def __await__(self):
yield self
def __repr__(self):
return '%s(until=%.1f)' % (self.__class__.__name__, self.until)
คลาสนี้จัดเก็บเฉพาะเหตุการณ์เท่านั้นไม่ได้บอกว่าจะจัดการอย่างไร
คุณสมบัติพิเศษเพียงอย่างเดียวคือ__await__
- เป็นสิ่งที่await
คีย์เวิร์ดมองหา ในทางปฏิบัติมันเป็นเครื่องวนซ้ำ แต่ไม่สามารถใช้ได้กับเครื่องจักรการทำซ้ำแบบปกติ
2.2.1. กำลังรอกิจกรรม
ตอนนี้เรามีเหตุการณ์แล้วโครูทีนมีปฏิกิริยาอย่างไร? เราควรจะสามารถแสดงสิ่งที่เทียบเท่าได้sleep
โดยการเข้าawait
ร่วมกิจกรรมของเรา เพื่อดูว่าเกิดอะไรขึ้นเรารอสองครั้งครึ่งหนึ่ง:
import time
async def asleep(duration: float):
"""await that ``duration`` seconds pass"""
await AsyncSleep(time.time() + duration / 2)
await AsyncSleep(time.time() + duration / 2)
เราสามารถสร้างอินสแตนซ์และเรียกใช้โครูทีนนี้ได้โดยตรง คล้ายกับเครื่องกำเนิดไฟฟ้าโดยใช้โครูcoroutine.send
ทีนจนกว่าจะได้yield
ผลลัพธ์
coroutine = asleep(100)
while True:
print(coroutine.send(None))
time.sleep(0.1)
สิ่งนี้ทำให้เรามีสองAsyncSleep
เหตุการณ์และStopIteration
เมื่อโครูทีนเสร็จสิ้น สังเกตว่าความล่าช้าเพียงอย่างเดียวมาจากtime.sleep
ในลูป! แต่ละรายการAsyncSleep
จะจัดเก็บเฉพาะค่าชดเชยจากเวลาปัจจุบัน
2.2.2. เหตุการณ์ + นอน
ณ จุดนี้เรามีกลไกสองอย่างที่แยกจากกัน:
AsyncSleep
เหตุการณ์ที่เกิดขึ้นได้จากภายในโครูทีน
time.sleep
ที่รอได้โดยไม่ส่งผลกระทบต่อโครูทีน
โดยเฉพาะอย่างยิ่งสองสิ่งนี้เป็นมุมฉาก: ไม่มีใครส่งผลกระทบหรือกระตุ้นอีกฝ่าย เป็นผลให้เราสามารถกำหนดกลยุทธ์ของเราเองsleep
เพื่อตอบสนองความล่าช้าของAsyncSleep
ไฟล์.
2.3. ห่วงเหตุการณ์ไร้เดียงสา
หากเรามีโครูทีนหลายตัวแต่ละตัวสามารถบอกเราได้ว่าต้องการปลุกเมื่อใด จากนั้นเราสามารถรอจนกว่าพวกเขาคนแรกที่ต้องการจะกลับมาทำงานต่อจากนั้นสำหรับหนึ่งในภายหลังและอื่น ๆ โดยเฉพาะอย่างยิ่งในแต่ละจุดที่เราจะดูแลเกี่ยวกับเป็นที่หนึ่งต่อไป
สิ่งนี้ทำให้การตั้งเวลาตรงไปตรงมา:
- เรียงลำดับตามเวลาตื่นนอนที่ต้องการ
- เลือกคนแรกที่อยากตื่น
- รอจนกว่าจะถึงเวลานี้
- เรียกใช้โครูทีนนี้
- ทำซ้ำจาก 1.
การใช้งานเล็กน้อยไม่จำเป็นต้องมีแนวคิดขั้นสูงใด ๆ A list
อนุญาตให้จัดเรียงโครูทีนตามวันที่ time.sleep
รอเป็นปกติ การรันโครูทีนใช้งานได้เหมือนก่อนหน้าcoroutine.send
นี้
def run(*coroutines):
"""Cooperatively run all ``coroutines`` until completion"""
waiting = [(0, coroutine) for coroutine in coroutines]
while waiting:
until, coroutine = waiting.pop(0)
time.sleep(max(0.0, until - time.time()))
try:
command = coroutine.send(None)
except StopIteration:
continue
if isinstance(command, AsyncSleep):
waiting.append((command.until, coroutine))
waiting.sort(key=lambda item: item[0])
แน่นอนว่านี่มีพื้นที่กว้างขวางสำหรับการปรับปรุง เราสามารถใช้ฮีปสำหรับคิวรอหรือตารางจัดส่งสำหรับเหตุการณ์ เรายังสามารถดึงค่าที่ส่งกลับจากStopIteration
และกำหนดให้กับโครูทีน อย่างไรก็ตามหลักการพื้นฐานยังคงเหมือนเดิม
2.4. รอสหกรณ์
AsyncSleep
เหตุการณ์และrun
ห่วงเหตุการณ์ที่มีการดำเนินงานที่ทำงานอย่างเต็มที่ของเหตุการณ์หมดเวลา
async def sleepy(identifier: str = "coroutine", count=5):
for i in range(count):
print(identifier, 'step', i + 1, 'at %.2f' % time.time())
await asleep(0.1)
run(*(sleepy("coroutine %d" % j) for j in range(5)))
สิ่งนี้จะสลับไปมาระหว่างโครูทีนทั้งห้าแบบร่วมมือกันหยุดการทำงานแต่ละครั้งเป็นเวลา 0.1 วินาที แม้ว่าลูปเหตุการณ์จะซิงโครนัส แต่ก็ยังดำเนินการทำงานใน 0.5 วินาทีแทนที่จะเป็น 2.5 วินาที โครูทีนแต่ละตัวมีสถานะและทำหน้าที่อย่างอิสระ
3. I / O ลูปเหตุการณ์
ห่วงเหตุการณ์ที่สนับสนุนsleep
เหมาะสำหรับการลงคะแนนเลือกตั้ง อย่างไรก็ตามการรอ I / O บนที่จับไฟล์สามารถทำได้อย่างมีประสิทธิภาพมากขึ้น: ระบบปฏิบัติการใช้ I / O และทำให้รู้ว่าแฮนเดิลใดพร้อมใช้งาน ตามหลักการแล้วการวนซ้ำของเหตุการณ์ควรสนับสนุนเหตุการณ์ "พร้อมสำหรับ I / O" ที่ชัดเจน
3.1. select
โทร
Python มีอินเทอร์เฟซสำหรับสืบค้น OS สำหรับการจัดการการอ่าน I / O อยู่แล้ว เมื่อเรียกด้วยแฮนเดิลเพื่ออ่านหรือเขียนมันจะส่งกลับที่จับพร้อมที่จะอ่านหรือเขียน:
readable, writeable, _ = select.select(rlist, wlist, xlist, timeout)
ตัวอย่างเช่นเราสามารถopen
เขียนไฟล์และรอให้พร้อม:
write_target = open('/tmp/foo')
readable, writeable, _ = select.select([], [write_target], [])
เมื่อเลือกผลตอบแทนแล้วจะwriteable
มีไฟล์ที่เปิดอยู่
3.2. เหตุการณ์ I / O พื้นฐาน
คล้ายกับAsyncSleep
คำขอเราจำเป็นต้องกำหนดเหตุการณ์สำหรับ I / O ด้วยselect
ตรรกะพื้นฐานเหตุการณ์ต้องอ้างถึงวัตถุที่อ่านได้ - พูดopen
ไฟล์ นอกจากนี้เราจัดเก็บข้อมูลที่จะอ่าน
class AsyncRead:
def __init__(self, file, amount=1):
self.file = file
self.amount = amount
self._buffer = ''
def __await__(self):
while len(self._buffer) < self.amount:
yield self
self._buffer += self.file.read(1)
return self._buffer
def __repr__(self):
return '%s(file=%s, amount=%d, progress=%d)' % (
self.__class__.__name__, self.file, self.amount, len(self._buffer)
)
เช่นเดียวกับที่AsyncSleep
เราจัดเก็บข้อมูลที่จำเป็นสำหรับการเรียกใช้ระบบพื้นฐานเป็นส่วนใหญ่ คราว__await__
นี้สามารถกลับมาอ่านซ้ำได้หลายครั้งจนกว่าจะamount
อ่านสิ่งที่ต้องการ นอกจากนี้เราreturn
ยังให้ผลลัพธ์ I / O แทนที่จะดำเนินการต่อ
3.3. การเพิ่มลูปเหตุการณ์ด้วยการอ่าน I / O
พื้นฐานสำหรับการวนซ้ำเหตุการณ์ของเรายังคงเป็นที่run
กำหนดไว้ก่อนหน้านี้ อันดับแรกเราต้องติดตามคำขออ่าน นี่ไม่ใช่กำหนดการที่จัดเรียงอีกต่อไปเราทำแผนที่เฉพาะคำขออ่านเพื่อจัดเรียง
waiting_read = {}
เนื่องจากselect.select
ใช้พารามิเตอร์การหมดเวลาเราจึงสามารถใช้แทนtime.sleep
ได้
time.sleep(max(0.0, until - time.time()))
readable, _, _ = select.select(list(reads), [], [])
สิ่งนี้ทำให้เราสามารถอ่านไฟล์ได้ทั้งหมด - หากมีเราจะเรียกใช้โครูทีนที่เกี่ยวข้อง หากไม่มีเรารอนานพอที่โครูทีนปัจจุบันของเราจะทำงาน
if readable:
waiting.append((until, coroutine))
waiting.sort()
coroutine = waiting_read[readable[0]]
สุดท้ายเราต้องฟังคำขออ่านจริงๆ
if isinstance(command, AsyncSleep):
...
elif isinstance(command, AsyncRead):
...
3.4. วางไว้ด้วยกัน
ข้างต้นเป็นการทำให้เข้าใจง่ายขึ้นเล็กน้อย เราจำเป็นต้องสลับไปมาเพื่อไม่ให้อดอาหารถ้าเราสามารถอ่านได้ตลอดเวลา เราจำเป็นต้องจัดการโดยไม่มีอะไรจะอ่านหรือไม่มีอะไรให้รอ อย่างไรก็ตามผลลัพธ์สุดท้ายยังคงอยู่ใน 30 LOC
def run(*coroutines):
"""Cooperatively run all ``coroutines`` until completion"""
waiting_read = {}
waiting = [(0, coroutine) for coroutine in coroutines]
while waiting or waiting_read:
try:
until, coroutine = waiting.pop(0)
except IndexError:
until, coroutine = float('inf'), None
readable, _, _ = select.select(list(waiting_read), [], [])
else:
readable, _, _ = select.select(list(waiting_read), [], [], max(0.0, until - time.time()))
if readable and time.time() < until:
if until and coroutine:
waiting.append((until, coroutine))
waiting.sort()
coroutine = waiting_read.pop(readable[0])
try:
command = coroutine.send(None)
except StopIteration:
continue
if isinstance(command, AsyncSleep):
waiting.append((command.until, coroutine))
waiting.sort(key=lambda item: item[0])
elif isinstance(command, AsyncRead):
waiting_read[command.file] = coroutine
3.5. I / O สหกรณ์
ขณะAsyncSleep
นี้AsyncRead
และrun
การใช้งานสามารถใช้งานได้อย่างสมบูรณ์เพื่อเข้าสู่โหมดสลีปและ / หรืออ่าน เช่นเดียวกับsleepy
เราสามารถกำหนดผู้ช่วยเพื่อทดสอบการอ่าน:
async def ready(path, amount=1024*32):
print('read', path, 'at', '%d' % time.time())
with open(path, 'rb') as file:
result = return await AsyncRead(file, amount)
print('done', path, 'at', '%d' % time.time())
print('got', len(result), 'B')
run(sleepy('background', 5), ready('/dev/urandom'))
เมื่อเรียกใช้สิ่งนี้เราจะเห็นว่า I / O ของเราถูกแทรกแซงด้วยงานที่รออยู่:
id background round 1
read /dev/urandom at 1530721148
id background round 2
id background round 3
id background round 4
id background round 5
done /dev/urandom at 1530721148
got 1024 B
4. Non-Blocking I / O
ในขณะที่ I / O ในแฟ้มได้รับแนวคิดข้ามมันไม่ได้จริงๆเหมาะสำหรับห้องสมุดเหมือนasyncio
ที่: select
โทรผลตอบแทนเสมอสำหรับไฟล์และทั้งสองopen
และread
อาจป้องกันการไปเรื่อย ๆ สิ่งนี้จะบล็อกโครูทีนทั้งหมดของลูปเหตุการณ์ซึ่งไม่ดี ไลบรารีเช่นaiofiles
ใช้เธรดและการซิงโครไนซ์กับ I / O ที่ไม่ปิดกั้นปลอมและเหตุการณ์ในไฟล์
อย่างไรก็ตามซ็อกเก็ตอนุญาตให้มี I / O แบบไม่ปิดกั้น - และเวลาแฝงที่มีมาโดยกำเนิดทำให้มีความสำคัญมากขึ้น เมื่อใช้ในการวนซ้ำของเหตุการณ์คุณสามารถรวมการรอข้อมูลและการลองใหม่ได้โดยไม่ปิดกั้นสิ่งใด
4.1. เหตุการณ์ I / O ที่ไม่ปิดกั้น
เช่นเดียวกับของAsyncRead
เราเราสามารถกำหนดเหตุการณ์ระงับและอ่านสำหรับซ็อกเก็ต แทนที่จะใช้ไฟล์เราใช้ซ็อกเก็ตซึ่งต้องไม่ปิดกั้น นอกจากนี้การ__await__
ใช้งานของเราsocket.recv
แทนfile.read
.
class AsyncRecv:
def __init__(self, connection, amount=1, read_buffer=1024):
assert not connection.getblocking(), 'connection must be non-blocking for async recv'
self.connection = connection
self.amount = amount
self.read_buffer = read_buffer
self._buffer = b''
def __await__(self):
while len(self._buffer) < self.amount:
try:
self._buffer += self.connection.recv(self.read_buffer)
except BlockingIOError:
yield self
return self._buffer
def __repr__(self):
return '%s(file=%s, amount=%d, progress=%d)' % (
self.__class__.__name__, self.connection, self.amount, len(self._buffer)
)
ในทางตรงกันข้ามกับAsyncRead
, __await__
ดำเนินการอย่างแท้จริง non-blocking I / O เมื่อมีข้อมูลก็จะอ่านเสมอ เมื่อไม่มีข้อมูลก็จะระงับเสมอ นั่นหมายถึงการวนซ้ำเหตุการณ์จะถูกบล็อกในขณะที่เราทำงานที่มีประโยชน์
4.2. ยกเลิกการปิดกั้นลูปเหตุการณ์
เท่าที่เป็นห่วงเหตุการณ์ไม่มีอะไรเปลี่ยนแปลงมากนัก เหตุการณ์ที่จะรับฟังยังคงเหมือนกับไฟล์ - ตัวอธิบายไฟล์ที่ทำเครื่องหมายว่าพร้อมselect
แล้ว
elif isinstance(command, AsyncRead):
waiting_read[command.file] = coroutine
elif isinstance(command, AsyncRead):
waiting_read[command.file] = coroutine
elif isinstance(command, AsyncRecv):
waiting_read[command.connection] = coroutine
ณ จุดนี้น่าจะชัดเจนAsyncRead
และAsyncRecv
เป็นเหตุการณ์ประเภทเดียวกัน เราสามารถ refactor ให้เป็นเหตุการณ์เดียวด้วยส่วนประกอบ I / O ที่แลกเปลี่ยนได้ ผลที่ตามมาการวนซ้ำเหตุการณ์โครูทีนและเหตุการณ์จะแยกตัวกำหนดตารางเวลารหัสกลางโดยพลการและ I / O จริงอย่างชัดเจน
4.3. ด้านที่น่าเกลียดของ I / O ที่ไม่ปิดกั้น
ในหลักการสิ่งที่คุณควรจะทำอย่างไรที่จุดนี้จะทำซ้ำตรรกะของการread
เป็นสำหรับrecv
AsyncRecv
อย่างไรก็ตามสิ่งนี้น่าเกลียดกว่ามากในตอนนี้ - คุณต้องจัดการกับผลตอบแทนก่อนกำหนดเมื่อฟังก์ชันถูกบล็อกภายในเคอร์เนล แต่ให้การควบคุมคุณ ตัวอย่างเช่นการเปิดการเชื่อมต่อกับการเปิดไฟล์นั้นยาวกว่ามาก:
file = open(path, 'rb')
connection = socket.socket()
connection.setblocking(False)
try:
connection.connect((url, port))
except BlockingIOError:
pass
เรื่องสั้นขนาดยาวสิ่งที่เหลืออยู่คือการจัดการข้อยกเว้นไม่กี่สิบบรรทัด เหตุการณ์และการวนซ้ำของเหตุการณ์ทำงาน ณ จุดนี้แล้ว
id background round 1
read localhost:25000 at 1530783569
read /dev/urandom at 1530783569
done localhost:25000 at 1530783569 got 32768 B
id background round 2
id background round 3
id background round 4
done /dev/urandom at 1530783569 got 4096 B
id background round 5
ภาคผนวก
ตัวอย่างรหัสที่ github
BaseEventLoop
ใช้งานCPython : github.com/python/cpython/blob/…