2.32. Coroutines

A coroutine คือฟังก์ชันที่สามารถหยุดชั่วคราวระหว่างทางแล้วกลับมาทำงานต่อจากจุดที่หยุดไว้ โดยตัวแปรโลคอลทั้งหมดยังคงอยู่ครบ มันสะท้อนรูปแบบของเจนเนอเรเตอร์ นั่นคือฟังก์ชันที่สามารถระงับและกลับมาทำงานต่อได้ โดยมีความแตกต่างตรงที่ใครเป็นผู้ขับเคลื่อนการกลับมาทำงานต่อแต่ละครั้ง

ไวยากรณ์ของ Python สำหรับเขียน coroutine คือคู่คีย์เวิร์ด async / await โดย async ทำเครื่องหมายว่าฟังก์ชันนั้นเป็น coroutine ส่วน await ทำเครื่องหมายจุดภายในฟังก์ชันที่อนุญาตให้หยุดชั่วคราวได้

2.32.1. การนิยาม coroutine

ฟังก์ชันที่นิยามด้วย async def คือ coroutine function การเรียกใช้งานมันจะไม่รันตัวฟังก์ชัน แต่จะคืนค่า coroutine object ที่ยังไม่ได้เริ่มทำงาน:

async def greet(name):
    print("hello,", name)

coro = greet("Alice")        # body NOT run yet

coroutine object จะถูกหยุดชั่วคราวตั้งแต่ต้นของฟังก์ชัน ต้องมีบางอย่างมา ขับเคลื่อน มันเพื่อให้ตัวฟังก์ชันทำงาน สิ่งนั้นคือ event loop ซึ่งเป็น runtime component MicroPython มี event loop อยู่ในโมดูล asyncio สำหรับตอนนี้ให้มองว่า coroutine คือ "พร้อมทำงาน รอผู้ขับเคลื่อน"

2.32.2. การหยุดชั่วคราวภายใน coroutine

ภายใน coroutine นิพจน์ await จะระงับการทำงานจนกว่าค่าที่รอจะพร้อม:

async def fetch_and_log():
    data = await read_sensor()
    print("got:", data)

เมื่อตัวฟังก์ชันถึง await read_sensor() coroutine จะส่งคืนการควบคุมให้กับสิ่งที่กำลังรันมันอยู่ เมื่อ read_sensor() ทำงานเสร็จ ตัวขับเคลื่อนจะกลับมาทำงาน coroutine ต่อในบรรทัดถัดไป โดยมีผลลัพธ์ผูกไว้กับ data

await ใช้ได้เฉพาะภายใน coroutine เท่านั้น การใช้มันในฟังก์ชันปกติจะเกิด syntax error

2.32.3. ความสัมพันธ์กับเจนเนอเรเตอร์

Coroutine และเจนเนอเรเตอร์ใช้กลไกพื้นฐานเดียวกัน ความแตกต่างอยู่ที่ใครเป็นผู้ดึงการกลับมาทำงานต่อแต่ละครั้ง:

  • เจนเนอเรเตอร์ yield ค่า ผู้ใช้งานดึงค่าถัดไปด้วย next() หรือโดยการวนซ้ำ

  • coroutine yield การควบคุม event loop กำหนดเวลาการกลับมาทำงานต่อเมื่อการดำเนินการที่รอไว้พร้อมแล้ว

หากเข้าใจการส่งต่อระหว่าง generator กับ yield แล้ว การส่งต่อของ coroutine ก็เป็นแนวคิดเดียวกัน เพียงแต่ขับเคลื่อนโดย event loop แทน for loop

event loop คือ dispatcher ขนาดเล็กที่เก็บรายการ coroutine ที่กำลังรอสิ่งใดสิ่งหนึ่ง (ตัวจับเวลา, เหตุการณ์ network, coroutine อื่นทำงานเสร็จ) ในแต่ละรอบจะเลือก coroutine ที่เงื่อนไขการรอได้รับการตอบสนองแล้ว กลับมาทำงานต่อจนถึง await ถัดไป จากนั้นบันทึกสิ่งที่ coroutine นั้นกำลังรออยู่และเดินหน้าต่อไปยัง coroutine อื่นที่พร้อมทำงาน ผลลัพธ์คือ task หลายอย่างคืบหน้าไปพร้อมกันบน thread เดียว แต่ละ coroutine สละการควบคุมโดยสมัครใจที่จุด await ของมัน และ loop จะเติมช่วงเวลาเหล่านั้นด้วย coroutine อื่นที่พร้อมทำงาน

ภายใต้เบื้องหลัง await และ yield ใช้ฟีเจอร์ Python runtime เดียวกันในการระงับและกลับมาทำงานต่อของฟังก์ชัน คีย์เวิร์ดต่างกันเพราะแบบแผนที่ล้อมรอบต่างกัน: yield ส่งค่าคืนให้ผู้ใช้งานที่ดึงด้วย next(); await ส่งการควบคุมให้ event loop ที่กำหนดเวลาการกลับมาทำงานต่อเมื่อการดำเนินการที่รอไว้พร้อม async / await โดยพื้นฐานคือไวยากรณ์ใหม่กว่าสำหรับรูปแบบ coroutine ไลบรารีรุ่นเก่าสร้าง coroutine บน generator machinery โดยตรง โดยใช้ yield from (แนะนำใน Iterators และ generators) เพื่อมอบหมายการระงับระหว่าง coroutine

2.32.4. Coroutine ต้องการตัวขับเคลื่อน

coroutine ไม่มีชีวิตหาก runtime ไม่ขับเคลื่อนมัน การนิยามไว้ไม่เป็นปัญหา แต่การรันต้องมี event loop โมดูล asyncio ของ MicroPython จัดเตรียม event loop นั้น ส่วน Asyncio ครอบคลุมวิธีเริ่ม loop, กำหนดเวลา coroutine บนมัน, แบ่งปันสถานะระหว่างกันด้วย lock และ event, จัดการการยกเลิกและ timeout และจัดรูปแบบแอปพลิเคชันจริงรอบคีย์เวิร์ด async / await ที่แนะนำไว้ที่นี่