|
async/awaitは、非同期処理を簡潔に書くための構文。
非同期処理とは、時間がかかる処理をブロックせずに実行すること。
(自分のイメージでは、時間がかかる処理を別スレッドで実行することだが、必ずしもそうではないようで、例えばI/O待ちのときに他の処理を行えるようにする事も含むらしいらしい)
Rustでは、非同期処理のためにFutureトレイトというものが用意されている。
非同期処理を行う関数は、Futureを返す。
非同期処理の結果は、Futureから取得する形となる。
このFutureを隠蔽する構文がasync/await。
ただ、Rustの標準ライブラリーではFutureトレイト(とasync/awaitの構文)を提供しているだけで、Futureの実体には別のライブラリーが提供しているものを使うらしい。
その代表がtokio。他にfuturesやasync-stdといったクレートがあるらしい。
非同期処理を行う関数(やメソッド)には、asyncを付ける。
pub(crate) async fn task1() -> i32 { tokio::time::sleep(Duration::from_secs(2)).await; 123 }
asyncの付いた関数(やメソッド)を呼び出すには、awaitを付ける。
(awaitを呼び出すことで、非同期処理が開始されるらしい)
async fn call() { let r /*i32*/ = task1().await; }
そして、awaitを付けて関数を呼び出す場合、呼び出す側の関数もasyncである必要がある。
そうなると、awaitの付いた関数を呼び出していると、それを呼び出す関数はasyncを付ける必要があり、
その関数を呼び出すにはawaitを付ける必要があり、それを呼び出す関数はasyncを付ける必要があり、…
を繰り返して、最終的にmain関数にもasyncを付けたくなる。
が、標準ではmain関数にasyncは付けられないので、#[tokio::main]を使うことになる。
#[tokio::main] async fn main() { 〜 }
同様に、テストのときはtokio::testを使う。
#[cfg(test)] mod test{ use tokio::test; #[test] async fn test1(){ 〜 } }
ブロックを使う場合もasyncを付ける。
let future = async { tokio::time::sleep(Duration::from_secs(2)).await; 456 }; let r /*i32*/ = future.await;
複数の非同期関数を同時に待つには、tokioのjoin!マクロを使う。
let r /*(i32, i32)*/ = tokio::join!(task1(), task2());
非同期処理(asyncが付いている関数)を同期処理(asyncの付いていない関数)から呼ぶには、block_onを使う。
ただし、同一スレッド内でネストしてblock_onを使うことは出来ない。
つまり、同一スレッド内では、block_onのブロックの中で、もう一度block_onを呼び出すことは出来ない。
そして、実行する関数に#[tokio::main]や#[tokio::test]が付けられていると、実際にはblock_onが使われている。
(#[tokio::main]や#[tokio::test]はマクロであり、block_onを使ったプログラムに展開される)
したがって、自分のプログラムでは単純にblock_onを使うことは出来ない。
ちょっと無理矢理な方法ではあるが、別スレッドを起動すれば、block_onを使うことが出来る。
#[tokio::main] async fn main() { std::thread::scope(|scope| { scope.spawn(move || { let runtime = tokio::runtime::Runtime::new().unwrap(); runtime.block_on(async { println!("sleep start"); tokio::time::sleep(Duration::from_secs(2)).await; println!("sleep end") }); }); }); }
scope.spawn()で別スレッドを起動している。
std::thread::spawn()でもスレッドを起動することが出来るが、block_onの中にselfを含めたい場合、ライフタイムの問題でコンパイルエラーになる。
scope()はブロック内の処理が終了するまで待つらしく、コンパイラーがライフタイムを認識できるからエラーにならないようだ。
asyncを付けたメソッドを持つトレイトは、そのままでは構造体のフィールドに保持できない。[2024-12-19]
その場合は、traitと、そのトレイトを実装するimplに#[async_trait::async_trait]属性を付ける。
これらは同一のものを付ける必要がある。例えばasync_traitにオプションを付けているときは、それも含めて同じものにする。
〜 [dependencies] async-trait = "0.1.83"
#[async_trait::async_trait] trait MyTrait { async fn example(&self); } struct MyStruct1; #[async_trait::async_trait] impl MyTrait for MyStruct1 { async fn example(&self) { println!("example"); } } struct MyStruct2 { t1: Box<dyn MyTrait>, // トレイトをフィールドに保持 } impl MyStruct2 { async fn execute(&self) { self.t1.example().await; } } #[tokio::main] async fn main() { let s2 = MyStruct2 { t1: Box::new(MyStruct1 {}), }; s2.execute().await; }