luckyegg168/rust-skill

rust-async-runtime

Rust 非同步執行時技能。涵蓋 tokio 1.44 runtime 架構與選擇、async/await 語法模式、 Future trait 原理、常見陷阱(blocking in async、async trait)、 channel 通訊(mpsc/oneshot/broadcast/watch)、task spawn 策略、 select!

Zobacz źródło

Jak używać

  1. Skopiuj polecenie instalacji i sprawdź źródła przed instalacją we własnym środowisku agenta.
  2. W oryginale sprawdź wymagane narzędzia, dane dostępowe i obsługiwane środowiska.
  3. Opisz dane wejściowe i oczekiwany rezultat; porównaj wynik ze źródłami.
npx skills add https://github.com/luckyegg168/rust-skill --skill rust-async-runtime

Przed rozpoczęciem

  • Instalacja nie obejmuje subskrypcji modelu, limitu API ani uprawnień do plików.
  • Gwiazdki i instalacje pokazują aktywność, a nie niezależny test jakości lub bezpieczeństwa.
Oryginalny dokument Skill

Treść z repozytorium z zachowaniem nagłówków, przykładów, kodu, tabel, linków i obrazów.

Rust Async Runtime

適用場景

  • 需要高併發 I/O(HTTP 伺服器、資料庫連線、WebSocket)
  • 實作非同步任務排程、背景工作、定時器
  • 使用 channel 進行任務間通訊
  • 處理 async stream(串流資料處理、SSE、gRPC streaming)
  • 需要 graceful shutdown 的長駐服務
  • 在 async 環境中正確使用鎖與同步原語

核心知識

Runtime 選擇

Runtime特性適用場景
tokio最成熟、生態系最大、work-stealing 排程器生產環境首選,絕大多數專案
async-stdAPI 貼近 std、較輕量學習用途、小型專案
smol極簡、可組合、無 macro嵌入式或需要最小依賴的場景
結論:除非有明確理由,一律選 tokio。生態系中 95%+ 的 async crate 以 tokio 為主要支援目標。

tokio Runtime 類型

rust
// 多執行緒 runtime(預設),適合生產環境
#[tokio::main]
async fn main() { /* ... */ }

// 等同於:
fn main() {
    tokio::runtime::Builder::new_multi_thread()
        .enable_all()
        .build()
        .unwrap()
        .block_on(async { /* ... */ })
}

// 單執行緒 runtime,適合測試或低負載場景
#[tokio::main(flavor = "current_thread")]
async fn main() { /* ... */ }

選擇依據:

  • multi_thread:CPU 核心 > 1、需要平行處理、生產環境
  • current_thread:單元測試、嵌入式環境、確定性排程需求

async/await 基本原理

  1. async fn 回傳一個實作 Future trait 的匿名型別
  2. Future 是惰性的——宣告不會執行,必須被 .await 或交給 executor
  3. .await 讓出控制權給 runtime,直到 Future 完成
  4. Runtime(executor)負責輪詢(poll)所有 Future 直到 Poll::Ready
rust
// Future trait 核心簽名
trait Future {
    type Output;
    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
}

blocking in async 問題

絕對不要在 async 上下文中執行同步阻塞操作:

rust
// ❌ 錯誤:阻塞整個 executor 執行緒
async fn bad() {
    std::thread::sleep(Duration::from_secs(5)); // 阻塞!
    std::fs::read_to_string("large.txt").unwrap(); // 阻塞!
}

// ✅ 正確:使用 tokio 非同步版本
async fn good() {
    tokio::time::sleep(Duration::from_secs(5)).await;
    tokio::fs::read_to_string("large.txt").await.unwrap();
}

// ✅ 正確:無法避免同步操作時,用 spawn_blocking
async fn also_good() {
    let result = tokio::task::spawn_blocking(|| {
        heavy_cpu_computation()
    }).await.unwrap();
}

Channel 通訊模式

Channel 類型模式用途
mpsc多生產者 → 單消費者任務佇列、事件匯集
oneshot單次發送 → 單次接收請求-回應、任務結果回傳
broadcast單生產者 → 多消費者事件廣播、配置更新通知
watch單生產者 → 多消費者(僅最新值)狀態監控、設定熱更新

Task Spawn 策略

rust
// 1. tokio::spawn — 在 runtime 中啟動新任務(需要 'static + Send)
let handle = tokio::spawn(async move {
    do_work().await
});
let result = handle.await?;

// 2. tokio::spawn_blocking — 在專用執行緒池中執行同步阻塞程式碼
let result = tokio::task::spawn_blocking(move || {
    compute_heavy_stuff()
}).await?;

// 3. tokio::task::spawn_local — 不要求 Send,但僅限 LocalSet
let local = tokio::task::LocalSet::new();
local.run_until(async {
    tokio::task::spawn_local(async {
        // 可以使用非 Send 型別
    }).await.unwrap();
}).await;

select! 多路複用

rust
use tokio::select;

// 同時等待多個 Future,第一個完成的分支被執行,其餘被取消
select! {
    val = async_operation_a() => {
        println!("A 先完成: {val}");
    }
    val = async_operation_b() => {
        println!("B 先完成: {val}");
    }
    // 可選的 else 分支:所有分支都被 disable 時執行
    else => {
        println!("所有分支都不可用");
    }
}

注意:被取消的分支對應的 Future 會被 drop,確保你的 Future 是取消安全的(cancel-safe)。 mpsc::Receiver::recv() 是取消安全的,但 read_exact() 不是。

async Mutex 正確用法

rust
// ❌ 持有 std::sync::Mutex 跨 .await 點會導致死鎖
let guard = std_mutex.lock().unwrap();
some_async_op().await; // guard 仍被持有!
drop(guard);

// ✅ 方案一:縮小臨界區,不跨 .await
let data = {
    let guard = std_mutex.lock().unwrap();
    guard.clone()
}; // guard 在此已 drop
some_async_op().await;

// ✅ 方案二:需要跨 .await 持有鎖時,使用 tokio::sync::Mutex
let guard = tokio_mutex.lock().await;
some_async_op().await;
drop(guard);
經驗法則:不跨 .await 用 std::sync::Mutex(更快);跨 .await 用 tokio::sync::Mutex。

程式碼範例

Basic: async/await + tokio::spawn + JoinSet

rust
use std::time::Duration;
use tokio::task::JoinSet;

#[tokio::main]
async fn main() {
    // 啟動單一任務
    let handle = tokio::spawn(async {
        tokio::time::sleep(Duration::from_millis(100)).await;
        42
    });
    let result = handle.await.expect("任務 panic");
    println!("單一任務結果: {result}");

    // 使用 JoinSet 管理多個併發任務
    let mut set = JoinSet::new();
    for i in 0..5 {
        set.spawn(async move {
            tokio::time::sleep(Duration::from_millis(50 * i)).await;
            i * 10
        });
    }
    while let Some(res) = set.join_next().await {
        println!("任務完成: {}", res.expect("任務 panic"));
    }
}

Intermediate: channel 通訊 + select!

rust
use tokio::sync::{mpsc, oneshot};
use tokio::select;

/// 工作請求:附帶 oneshot 回傳通道
struct Request {
    payload: String,
    respond_to: oneshot::Sender<String>,
}

#[tokio::main]
async fn main() {
    let (tx, mut rx) = mpsc::channel::<Request>(32);

    // 工作者:接收請求並回傳結果
    let worker = tokio::spawn(async move {
        while let Some(req) = rx.recv().await {
            let response = format!("已處理: {}", req.payload);
            let _ = req.respond_to.send(response);
        }
    });

    // 發送三個請求
    for i in 0..3 {
        let (resp_tx, resp_rx) = oneshot::channel();
        tx.send(Request {
            payload: format!("任務-{i}"),
            respond_to: resp_tx,
        }).await.expect("channel 已關閉");

        // 使用 select! 加上超時
        select! {
            result = resp_rx => {
                println!("{}", result.expect("工作者已斷線"));
            }
            _ = tokio::time::sleep(std::time::Duration::from_secs(1)) => {
                println!("請求 {i} 超時");
            }
        }
    }
    drop(tx); // 關閉 channel,讓工作者結束
    worker.await.expect("工作者 panic");
}

Advanced: graceful shutdown + async stream + semaphore

rust
use std::time::Duration;
use tokio::sync::{mpsc, Semaphore};
use tokio::signal;
use tokio_stream::wrappers::ReceiverStream;
use tokio_stream::StreamExt;
use std::sync::Arc;

#[tokio::main]
async fn main() {
    let (shutdown_tx, mut shutdown_rx) = mpsc::channel::<()>(1);
    let semaphore = Arc::new(Semaphore::new(3)); // 最多 3 個併發

    // 資料來源 stream
    let (data_tx, data_rx) = mpsc::channel(64);
    let producer = tokio::spawn(async move {
        for i in 0u64.. {
            if data_tx.send(i).await.is_err() { break; }
            tokio::time::sleep(Duration::from_millis(100)).await;
        }
    });

    // 消費者:帶限流 + stream 處理
    let sem = semaphore.clone();
    let consumer = tokio::spawn(async move {
        let mut stream = ReceiverStream::new(data_rx)
            .map(|n| n * 2)
            .take(20); // 最多處理 20 筆
        while let Some(value) = stream.next().await {
            let permit = sem.clone().acquire_owned().await.expect("semaphore 已關閉");
            tokio::spawn(async move {
                println!("處理: {value}");
                tokio::time::sleep(Duration::from_millis(200)).await;
                drop(permit); // 釋放許可
            });
        }
    });

    // 等待 Ctrl+C 或消費者完成
    tokio::select! {
        _ = signal::ctrl_c() => {
            println!("\n收到 Ctrl+C,開始優雅關閉...");
        }
        _ = consumer => {
            println!("消費者處理完畢");
        }
    }
    drop(shutdown_tx);
    producer.abort();

    // 等待所有 semaphore 許可歸還(確認所有進行中任務完成)
    let _ = semaphore.acquire_many(3).await;
    println!("所有任務已完成,程式結束");
}

常見錯誤對照表

錯誤訊息原因修復方式
future cannot be sent between threads safely / X is not Sendtokio::spawn 要求 Future 為 Send,但持有了非 Send 型別(如 Rc、MutexGuard)改用 Arc 取代 Rc;縮小 MutexGuard 作用域使其不跨 .await;或使用 spawn_local
Cannot start a runtime from within a runtime在 async 區塊內呼叫 block_on() 導致巢狀 runtime移除內層 block_on(),改用 .await;若整合同步庫,用 spawn_blocking + 獨立 runtime
blocking annotated I/O must be called from the context of the Tokio runtime在 runtime 外部呼叫 tokio I/O 函式確保程式碼在 #[tokio::main] 或 Runtime::block_on 範圍內執行
the trait bound ... async_trait is not satisfied / async fn in trait 問題Rust edition < 2024 不支援原生 async fn in trait,或物件安全限制Edition 2024 原生支援 async fn in trait;需要 dyn Trait 時使用 #[trait_variant::make(SendTrait: Send)] 或 async-trait crate
死鎖:程式卡住不動持有 std::sync::Mutex 鎖跨 .await 點縮小臨界區避免跨 .await,或改用 tokio::sync::Mutex
capacity of channel is full / 發送端卡住mpsc channel 已滿,背壓未處理增大 buffer、加速消費端、用 try_send 非阻塞處理、或使用 unbounded_channel(小心 OOM)
JoinError: task panickedtokio::spawn 的任務發生 panic在 spawn 的 async 區塊內加 catch_unwind,或在 .await 時用 match 處理 JoinError

Cargo.toml 依賴模板

toml
[package]
name = "my-async-app"
version = "0.1.0"
edition = "2024"

[dependencies]
tokio = { version = "1.44", features = ["full"] }
tokio-stream = "0.1"
tokio-util = "0.7"            # CancellationToken、codec 等工具
futures = "0.3"                # FutureExt、StreamExt 擴充 trait
async-trait = "0.1"            # edition < 2024 的 async fn in trait 支援
pin-project-lite = "0.2"       # 安全的 pin projection

# 可選
reqwest = { version = "0.12", features = ["json"] }  # HTTP 客戶端
tower = "0.5"                  # Service 抽象與 middleware
tracing = "0.1"                # 結構化日誌
tracing-subscriber = "0.3"

[dev-dependencies]
tokio = { version = "1.44", features = ["full", "test-util"] }
注意:生產環境可只啟用所需 feature(如 rt-multi-thread, macros, net, time)以減少編譯時間。

參考來源

  1. Tokio Tutorial — 官方教學,涵蓋 spawning、channels、I/O
  2. Asynchronous Programming in Rust — Rust 官方 async book
  3. Tokio API Documentation — tokio 1.x 完整 API 參考
  4. Alice Ryhl — Actors with Tokio — channel-based actor 模式實戰
  5. Jon Gjengset — Decrusting the tokio crate — 深入 tokio 內部架構
  6. Rust Edition 2024 — async fn in trait — 原生 async trait 支援說明

Powiązane zadania

z tego samego repozytorium

Więcej Skills

Wszystkie Skills
luckyegg168
Społeczność

rust-crate-selection

Rust crate 選擇指南技能。按功能領域推薦經過實戰驗證的 crate: HTTP(reqwest/hyper/axum)、序列化(serde/serdejson/toml)、 CLI(clap/dialoguer/indicatif)、日誌(tracing/log/envlogger)、 加密(ring/rustls/argon2)、資料庫(sqlx/diesel/sea-orm)、 GUI(egui/iced/tauri)、測試(proptest/mockall/criterion)、 非同步(tokio/async-trait/futures)。 含選擇判斷標準、版本鎖定策略、Cargo.toml features 管理。 觸發關鍵詞:Rust crate, which crate, recommend crate, crate comparison, best Rust library, cargo dependency, version pinning, blessed.rs, lib.rs, crates.io, feature flags

instalacje
1
GitHub Stars
0
Aktualizacja
29 mar
luckyegg168
Społeczność

rust-design-patterns

Rust 慣用設計模式技能。涵蓋 Builder Pattern(型別安全構建器)、 Newtype Pattern(型別封裝與 Deref)、Typestate Pattern(編譯期狀態機)、 RAII Guard Pattern(資源管理)、Interior Mutability(Cell/RefCell/Mutex)、 Strategy Pattern(trait objects vs enum dispatch)、 Command Pattern、Observer Pattern(回呼與事件系統)、 Extension Trait Pattern、Sealed Trait Pattern、From/Into 慣用轉換。 觸發關鍵詞:Rust design pattern, builder, newtype, typestate, RAII, interior mutability, strategy pattern, trait object, enum dispatch, sealed trait, extension trait, From Into, guard pat

instalacje
1
GitHub Stars
0
Aktualizacja
29 mar
luckyegg168
Społeczność

rust-ffi-interop

Rust FFI 互操作技能。涵蓋 PyO3 0.23 Rust ↔ Python 互操作、 cbindgen 生成 C header、bindgen 綁定 C 函式庫、extern "C" ABI、 [repr(C)] 記憶體佈局、CString/CStr 安全轉換、 null pointer 處理、panic boundary(catchunwind)、 記憶體所有權跨語言邊界管理、WASM 目標編譯。 觸發關鍵詞:FFI, PyO3, cbindgen, bindgen, extern C, repr C, CString, C interop, Python binding, WASM, foreign function, shared library, cdylib, staticlib

instalacje
1
GitHub Stars
0
Aktualizacja
29 mar