8.2. Coroutine và task¶
Coroutine là đơn vị công việc mà một chương trình asyncio được xây dựng từ đó; task là cách một ứng dụng chạy nhiều coroutine đồng thời.
8.2.1. Coroutine¶
Một coroutine là một hàm được khai báo bằng async def
import asyncio
async def heartbeat(interval_ms):
while True:
print("tick")
await asyncio.sleep_ms(interval_ms)
Phần thân trông giống như một hàm thông thường, với một thành phần bổ sung: await. Bất cứ khi nào coroutine phải chờ một điều gì đó -- một lần sleep, một lần đọc mạng, một sự kiện được thiết lập -- nó awaits một biểu thức biết cách tạm dừng coroutine cho đến khi điều nó đang chờ đã sẵn sàng. Tại mỗi await, coroutine trả quyền điều khiển lại cho asyncio; asyncio tiếp tục nó từ cùng một điểm khi thao tác được await đã hoàn thành.
Module asyncio cung cấp hai hàm sleep:
asyncio.sleep()-- đối số tính bằng giây, chấp nhận số thực.asyncio.sleep_ms()-- đối số tính bằng mili giây, nhận số nguyên. Đây là phần mở rộng của MicroPython; thường là lựa chọn phù hợp trên camera vì các thông số thời gian trong firmware có dạng mili giây.
Một async def đơn thuần không làm gì khi gọi riêng nó. Gọi heartbeat(500) không thực thi phần thân; nó trả về một đối tượng coroutine mà asyncio phải lên lịch. Cách đơn giản nhất để lên lịch một coroutine là asyncio.run()
asyncio.run(heartbeat(500))
asyncio.run() khởi động vòng lặp sự kiện, lên lịch coroutine được truyền vào làm điểm vào cấp cao nhất, chạy vòng lặp cho đến khi coroutine đó trả về, rồi dọn dẹp vòng lặp. Đối với một coroutine đơn lẻ, đó là toàn bộ chương trình. Đối với nhiều coroutine, ứng dụng sẽ dùng đến task.
8.2.2. Task¶
Một task là lớp bọc của asyncio xung quanh một coroutine, có nghĩa là lên lịch coroutine này đồng thời với coroutine hiện tại và cho tôi tiếp tục. asyncio.create_task() tạo một task và trả về một đối tượng Task đại diện cho công việc đã được lên lịch:
task = asyncio.create_task(heartbeat("fast", 100))
Coroutine giờ đây nằm trong lịch của vòng lặp; người gọi chưa chờ nó. Task được trả về là tay cầm mà người gọi dùng sau đó để tương tác với công việc đang chạy đó.
Khi ứng dụng có tay cầm, nó có thể thực hiện ba việc:
Chờ task hoàn thành. Một
Taskbản thân nó là có thể await được.result = await tasktạm dừng coroutine hiện tại cho đến khi coroutine củatasktrả về, rồi tiếp tục với bất cứ điều gì coroutine đó trả về (hoặc phát lại bất cứ ngoại lệ nào nó đã phát).Hủy task.
task.cancel()lên lịch đểasyncio.CancelledErrorđược phát ra bên trong coroutine của task tạiawaittiếp theo của nó, cho nó cơ hội chạy mã dọn dẹp trong khốifinally. Trang về timeouts và hủy đề cập đến chi tiết.Nhận dạng nó sau.
asyncio.current_task()trả vềTaskcho coroutine đang chạy hiện tại. Hầu hết các tập lệnh không bao giờ gọi nó; nó xuất hiện trong công cụ đo lường và trong các trình xử lý ngoại lệ.
Tập lệnh không nhất thiết phải lưu tay cầm mỗi lần. Các task nền tạm thời mà ứng dụng khởi động và để chạy có thể bỏ giá trị trả về -- vòng lặp vẫn lên lịch chúng:
import asyncio
async def heartbeat(name, interval_ms):
while True:
print(name)
await asyncio.sleep_ms(interval_ms)
async def main():
asyncio.create_task(heartbeat("fast", 100))
asyncio.create_task(heartbeat("slow", 500))
await asyncio.sleep(5)
asyncio.run(main())
Hai lệnh gọi create_task lên lịch cả hai heartbeat mà không chờ một trong số chúng. Quyền điều khiển trả về ngay lập tức cho main, sau đó awaits một giấc ngủ năm giây. Trong khi nó ngủ, hai task heartbeat tiếp tục tiến triển; vòng lặp luân phiên qua bất kỳ task nào sẵn sàng chạy. Sau năm giây main trả về, vòng lặp dọn dẹp bất kỳ task nào vẫn còn hoạt động, và asyncio.run() trả về cho người gọi.
Lưu tay cầm bất cứ khi nào ứng dụng thực sự cần một trong ba thao tác trên. Trong thực tế điều đó có nghĩa là hầu như luôn luôn, vì tắt ứng dụng một cách gọn gàng đồng nghĩa với việc hủy các task nền mà nó đã tạo -- trang về hủy đề cập đến mẫu này.
8.2.3. Quy tắc hai dòng¶
Chương trình asyncio tối giản là hai dòng mà các ví dụ trên kết thúc bằng:
async def main():
...
asyncio.run(main())
Mọi thứ khác -- các task mà ứng dụng tạo ra, các nguyên thủy nó phối hợp chúng, các luồng nó mở -- xảy ra bên trong main (và bên trong các coroutine mà main tạo ra). Khi một tập lệnh vượt quá vòng lặp while True: csi0.snapshot() truyền thống của camera, câu trả lời không phải là gọi asyncio.run() ở nhiều nơi; mà là đưa công việc mới vào main như các task bổ sung.