Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Concurrency với async

.await tuần tự không tự nhiên đồng thời

Nhiều điểm .await liên tiếp trong cùng một async fn chạy tuần tự — điểm thứ hai chỉ bắt đầu sau khi điểm thứ nhất đã Ready hoàn toàn, giống hệt gọi hàm thường nối tiếp nhau. Tự bản thân async/.await không tạo ra tính đồng thời — nó chỉ là cách viết một future tạm dừng được ở những chỗ đang chờ, không phải phép màu tự động chạy song song mọi thứ có .await.

Chạy nhiều future đồng thời

Muốn nhiều future thật sự tiến triển xen kẽ nhau (không cái nào phải xong hẳn mới tới lượt cái tiếp theo), cần một trong hai cách:

  • spawn mỗi future thành một task riêng trên runtime — bộ lập lịch của runtime tự luân phiên poll từng task độc lập.
  • gộp nhiều future vào một tổ hợp poll cùng nhau trong một task duy nhất, thường qua macro join!/select!.

Cả hai cơ chế này đều không có trong thư viện chuẩn — chúng do crate runtime (tokio, async-std, futures) cung cấp, vì việc điều phối nhiều future đúng đắn (đặc biệt là poll đan xen an toàn, không vi phạm quy tắc Pin) là một bài toán runtime cần giải quyết trọn vẹn, không phải thứ std tự làm thay. Với một runtime như tokio, code trông như sau (minh hoạ cú pháp, không chạy được nếu thiếu crate tokio):

use tokio::join;

async fn tai_trang(url: &str) -> String {
    // giả lập gọi mạng
    format!("nội dung của {url}")
}

async fn tai_ca_hai() {
    let (a, b) = join!(tai_trang("a.example"), tai_trang("b.example"));
    println!("{a} — {b}");
}

join! chạy cả hai future đồng thời, chỉ trả về khi cả hai đã Ready — khác hẳn viết tai_trang("a...").await; tai_trang("b...").await; (tuần tự, cái thứ hai đợi cái thứ nhất xong hẳn mới bắt đầu).

select! chạy nhiều future đồng thời nhưng trả về ngay khi future đầu tiên hoàn thành, huỷ các future còn lại — hữu ích cho các tình huống như đặt timeout (chạy đua giữa “thao tác chính” và “đồng hồ đếm giờ”, cái nào xong trước thắng):

use tokio::select;
use tokio::time::{sleep, Duration};

async fn vi_du_timeout() {
    select! {
        ket_qua = tai_du_lieu() => println!("Xong: {ket_qua}"),
        _ = sleep(Duration::from_secs(5)) => println!("Hết thời gian chờ"),
    }
}

async fn tai_du_lieu() -> String {
    String::from("dữ liệu")
}