async
/.await
기초
async
/.await
는 동기적 코드처럼 생긴 비동기 함수들을 작성하는 데 쓰이는
러스트 내장 도구입니다. async
는 코드 블록을 Future
라는 트레잇을 구현하는
상태기계로 변환해줍니다. 동기적 메소드 안에서 블록하는 함수를 호출한다면 전체
스레드가 블록되지만, Future
는 블록될 때 스레드를 놓아버리므로 다른
Future
가 실행될 수 있습니다.
Cargo.toml
파일에 의존성을 추가해 봅시다.
[dependencies]
futures = "0.3"
비동기 함수를 만들기 위해, async fn
문법을 사용합니다.
#![allow(unused)] fn main() { async fn do_something() { /* ... */ } }
async fn
이 반환하는 값은 한 개의 Future
객체입니다. 코드가 실제로 동작하게
하려면, executor로 Future
객체를 실행해야 합니다.
// `block_on`는 제공받은 future를 실행하여 완성될 때까지 현재의 스레드를 // 블록한다. 다른 종류의 executor는 여러 개의 future를 같은 스레드 안에서 // 스케줄링을 한다던가 하는 식으로 보다 복잡하게 동작한다. use futures::executor::block_on; async fn hello_world() { println!("hello, world!"); } fn main() { let future = hello_world(); // 아무것도 출력되지 않음 block_on(future); // `future` 가 실행되어 "hello, world!"가 출력됨 }
async fn
안에서 Future
트레잇을 구현한 다른 타입이 완성(예시: 다른 async fn
의 출력 등)될 때까지 기다리려면 .await
을 사용하면 됩니다. block on
과
달리, .await
는 현재의 스레드를 블록하지 않고, 대신에 해당 future가 완성될
때까지 비동기적으로 기다립니다. 이렇게 하면 해당 future가 현재 진행될 수 없는
상황에서도 다른 태스크들이 실행될 수 있습니다.
예를 들어, 세 개의 async fn
(learn_song
, sing_song
그리고 dance
)이 있다고
칩시다.
async fn learn_song() -> Song { /* ... */ }
async fn sing_song(song: Song) { /* ... */ }
async fn dance() { /* ... */ }
노래를 배우고 부르며, 춤을 추기위한 방법 중에 하나는 각각을 수행할 때마다 블록하는 것입니다.
fn main() {
let song = block_on(learn_song());
block_on(sing_song(song));
block_on(dance());
}
그러나, 이 방법으로는 최선의 성능을 낼 수 없습니다. 오직 한 번에 한 가지만
한다구요! 우리가 노래를 부르기 전에 먼저 노래를 배워야 하는 것은 맞지만, 춤은
노래를 배우거나 부르면서도 출 수 있습니다. 이를 위해, 우리는 동시에 수행될 수
있는 두 개의 다른 async fn
을 만들면 됩니다.
async fn learn_and_sing() {
// 노래를 부르기 전에 노래를 배울 때까지 기다림.
// 스레드를 블록하지 않기 위해 `block_on` 대신에 `.await`을 사용한다. 이렇게
// 하면, `춤`을 동시에 출 수 있다.
let song = learn_song().await;
sing_song(song).await;
}
async fn async_main() {
let f1 = learn_and_sing();
let f2 = dance();
// `join!`은 `.await`와 비슷하지만 여러 개의 future를 동시에 기다릴 수 있다.
// `learn_and_sing` future에서 일시적으로 블록되었더라도, `dance` future는
// 현재의 스레드를 가져올 것이다. `dance`가 블록되면, `learn_and_sing`은 다시
// 스레드를 가져올 수 있다. 둘 다 블록되면, `async_main`이 블록되고,
// executor에게 스레드를 양보할 것이다.
futures::join!(f1, f2);
}
fn main() {
block_on(async_main());
}
이 예제에서, 노래 배우기는 노래 부르기보다 먼저 동작해야 하지만, 노래 배우기와
부르기는 춤추기와 같은 시간에 동작할 수 있습니다. 만약 learn_and_sing
안에서
learn_song().await
말고, block_on(learn_song())
을 사용했다면, 해당 스레드는
learn_song
이 동작하는 동안에는 아무것도 할 수 없었을 것이고, 그렇다면 춤추기를
노래와 동시에 수행할 수 없었을 것입니다. 하지만 우리는 learn_song
future를
.await
함으로써, learn_song
이 블록되었을지라도 다른 태스크들이 현재의
스래드에서 실행되게 할 수 있습니다. 이 방법으로, 여러 개의 future를 한 개의
스레드에서 동시에 실행하여 완성할 수 있습니다.