Coroutine, còn được gọi là tiểu trình, là một kỹ thuật chuyển đổi ngữ cảnh ở cấp độ người dùng. Về bản chất, nó cho phép các đoạn mã trong cùng một luồng thực thi luân phiên nhau bằng cách tạm dừng và tiếp tục trạng thái thực thi. Khác với tiến trình hay luồng hệ thống, coroutine không do hệ điều hành quản lý mà do lập trình viên kiểm soát hoàn toàn.
Có nhiều cách để triển khai coroutine trong Python:
- Thư viện
greenlet– giải pháp sớm nhưng ít được dùng hiện nay. - Từ khóa
yieldvàyield from. - Decorator
@asyncio.coroutine(từ Python 3.4). - Từ khóa
asyncvàawait(từ Python 3.5) – phương pháp được khuyến nghị.
1. Sử dụng greenlet
Greenlet là một thư viện bên ngoài hỗ trợ chuyển đổi ngữ cảnh thủ công giữa các hàm.
pip install greenlet
from greenlet import greenlet
def task_a():
print("Bước 1: in ra 1")
task_b.switch() # Chuyển sang hàm task_b
print("Bước 6: in ra 2")
task_b.switch()
def task_b():
print("Bước 4: in ra 3")
task_a.switch() # Quay lại task_a tại điểm dừng
print("Bước 8: in ra 4")
# Tạo đối tượng greenlet
task_a = greenlet(task_a)
task_b = greenlet(task_b)
task_a.switch() # Bắt đầu thực thi từ task_a
2. Dùng từ khóa yield
Bằng cách tận dụng generator, ta có thể tạo cơ chế coroutine đơn giản thông qua yield và yield from.
def sub_task():
yield 3
yield 4
def main_task():
yield 1
yield from sub_task() # Nhường quyền thực thi cho sub_task
yield 2
# Thực thi
for value in main_task():
print(value)
3. Sử dụng asyncio với @asyncio.coroutine
Từ Python 3.4, thư viện chuẩn asyncio cung cấp nền tảng xử lý bất đồng bộ. Với decorator @asyncio.coroutine, kết hợp yield from để mô phỏng hành vi IO không chặn.
import asyncio
@asyncio.coroutine
def download_image():
print("Đang tải hình ảnh... (1)")
yield from asyncio.sleep(2) # Giả lập thời gian chờ mạng
print("Tải xong (2)")
@asyncio.coroutine
def fetch_data():
print("Bắt đầu lấy dữ liệu (3)")
yield from asyncio.sleep(2)
print("Hoàn tất (4)")
# Đăng ký các tác vụ
tasks = [
asyncio.ensure_future(download_image()),
asyncio.ensure_future(fetch_data())
]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
4. Sử dụng async/await (Python 3.5+)
Đây là cú pháp hiện đại và dễ đọc hơn. Hàm được định nghĩa với async def trả về một đối tượng coroutine. Để thực thi, cần đưa vào vòng lặp sự kiện.
import asyncio
async def download_image():
print("Đang tải... (1)")
await asyncio.sleep(2)
print("Xong (2)")
async def fetch_data():
print("Lấy dữ liệu... (3)")
await asyncio.sleep(2)
print("Kết thúc (4)")
tasks = [
asyncio.ensure_future(download_image()),
asyncio.ensure_future(fetch_data())
]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
Lưu ý: Gọi func() chỉ tạo ra đối tượng coroutine, chưa thực thi nội dung. Cần sử dụng asyncio.run() (từ Python 3.7) để chạy.
async def hello():
print("Xin chào!")
coro = hello()
asyncio.run(coro) # Bắt buộc để thực thi
Ví dụ minh họa chi tiết
import asyncio
async def background_task():
print("Khởi động tác vụ nền")
await asyncio.sleep(2)
print("Tác vụ nền hoàn tất")
return "Kết quả thành công"
async def main():
print("Bắt đầu chương trình chính")
result = await background_task()
print(f"Nhận được: {result}")
asyncio.run(main())
5. Các đối tượng có thể await
Từ khóa await chỉ làm việc với ba loại đối tượng:
- Coroutine object: Trả về bởi hàm
async def. - Future: Đại diện cho kết quả trong tương lai.
- Task: Một coroutine đã được lên lịch thực thi.
async def get_result():
print("Bắt đầu...")
res1 = await background_task()
print(f"Kết quả 1: {res1}")
res2 = await background_task()
print(f"Kết quả 2: {res2}")
6. Quản lý tác vụ với Task
Để chạy nhiều coroutine đồng thời, nên đóng gói chúng thành Task để thêm vào vòng lặp sự kiện.
async def main():
print("Tạo và chạy song song các tác vụ")
task1 = asyncio.create_task(background_task())
task2 = asyncio.create_task(background_task())
await task1
await task2
asyncio.run(main())
Trong Python trước 3.7, thay asyncio.create_task() bằng asyncio.ensure_future().