Yomiage
メニュー

Rust実践講座 16:async・awaitと通信の期限

更新 2026/9/27 6:22:11 公開

投稿者: しゅりりん

到達点は、非同期処理を動かす仕組みと、待ちを制限する場所を理解することです。前提は第07・15回です。

Futureだけでは進まない

async関数はFutureを作り、実行を進めるにはポーリングするランタイムが必要です。awaitは待ちが必要なときに他の仕事へ進む機会を与えます。ここではTokioを使い、ローカルのTCP通信を一往復させます。

新しいプロジェクトのCargo.tomlを次にします。

[package]
name = "async-practice"
version = "0.1.0"
edition = "2024"

[dependencies]
tokio = { version = "=1.48.0", features = ["rt-multi-thread", "macros", "net", "io-util", "time"] }

src/main.rsへ保存します。

use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};
use tokio::time::{timeout, Duration};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    timeout(Duration::from_secs(3), async {
        let listener = TcpListener::bind("127.0.0.1:0").await?;
        let address = listener.local_addr()?;
        let server = tokio::spawn(async move {
            let (mut socket, _) = listener.accept().await?;
            let mut request = [0; 4];
            socket.read_exact(&mut request).await?;
            if &request != b"ping" {
                return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "bad request"));
            }
            socket.write_all(b"pong").await?;
            Ok::<(), std::io::Error>(())
        });
        let mut client = TcpStream::connect(address).await?;
        client.write_all(b"ping").await?;
        let mut response = [0; 4];
        client.read_exact(&mut response).await?;
        println!("{}", std::str::from_utf8(&response)?);
        server.await??;
        Ok::<(), Box<dyn std::error::Error>>(())
    }).await??;
    Ok(())
}

cargo runでpongが出ます。TCPはバイトの流れで、一回のwriteが一回のreadに対応する保証はありません。ここでは長さを4バイトと決め、read_exactで必要な分を集めています。これは固定長の実習用プロトコルです。HTTPを扱う実アプリでは対応ライブラリを使い、HTTPの境界を自作で推測しません。

タスクの寿命も設計する

timeoutは囲んだFutureの待ちを制限しますが、別にspawnした仕事を自動で全部取り消すわけではありません。この短いアプリはmain終了時にランタイムも終了します。長く動くサーバーならJoinHandleを保持し、キャンセル、待ち合わせ、接続終了を設計します。

CPUを長く占有する計算や同期I/Oを、非同期タスク内でそのまま実行すると他の仕事を妨げます。spawn_blockingや専用ワーカーを検討します。共有ロックを持ったままawaitしないよう、必要な値を取り出してから待つ設計も重要です。

練習と解答

練習:通信相手が3バイトしか送らず接続を閉じたら、read_exactは成功するでしょうか。

解答:4バイトに足りないので失敗します。途中のデータを完全な応答と誤認しないため、メッセージの長さ、最大サイズ、期限をプロトコルとして決めます。

公式資料

Tokio Tutorialとtimeoutが参照先です。


Rust全20回の目次 | 前の回 | 次の回

このブラウザでは Web Speech API が利用できません。

1 / 18 · 停止中