Sử dụng Coroutine trong Python

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 yieldyield from.
  • Decorator @asyncio.coroutine (từ Python 3.4).
  • Từ khóa asyncawait (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 yieldyield 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().

Thẻ: python AsyncIO coroutine async-await Concurrency

Đăng vào ngày 7 tháng 7 lúc 17:44