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!

소스 보기

사용 방법

  1. 설치 명령을 복사하고 자신의 에이전트 환경에 설치하기 전에 소스 패키지를 확인하세요.
  2. 필요한 도구, 인증 정보, 지원 환경은 원본 문서에서 확인하세요.
  3. 입력과 원하는 산출물을 설명하고 출처와 결과를 대조하세요.
npx skills add https://github.com/luckyegg168/rust-skill --skill rust-async-runtime

시작 전 확인

  • 설치에는 모델 구독, API 한도, 파일 접근 권한이 포함되지 않습니다.
  • 별점 수와 설치 수는 활동 지표이며 독립적인 품질·보안 검증이 아닙니다.
원본 Skill 문서

원본 저장소의 제목, 예시, 코드, 표, 링크, 이미지를 유지해 표시합니다.

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 支援說明

관련 작업

같은 저장소의 Skills

더 많은 Skills

모든 Skills
luckyegg168
커뮤니티

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

설치 수
1
GitHub Stars
0
업데이트
3월 29일
luckyegg168
커뮤니티

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

설치 수
1
GitHub Stars
0
업데이트
3월 29일
luckyegg168
커뮤니티

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

설치 수
1
GitHub Stars
0
업데이트
3월 29일