0Pricing
Learn Rust Coding · 강의

퓨처와 작업 다루기

`Future` 트레이트와 비동기 런타임 환경에서 작업이 예약되고 관리되는 방식을 이해합니다.

퓨처와 작업 다루기은(는) CoddyKit의 무료 Learn Rust Coding 강의입니다. 이것은 3개 중 3번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 Learn Rust Coding 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. Learn Rust Coding 강의에는 총 3개의 강의가 포함되어 있습니다.

Rust Future란 무엇인가요?

비동기 Rust에서 Future는 언젠가 완료될 수 있는 비동기 계산을 나타내는 트레이트입니다. 아직 준비되지 않은 값을 위한 약속이라고 생각하시면 됩니다.

  • 비동기 Rust를 구성하는 핵심 요소입니다.
  • Rust의 async fn은 실제로 Future 트레이트를 구현하는 익명 타입을 반환합니다.
  • 값은 즉시 계산되지 않고, 실행기가 Future를 "폴링"할 때 계산됩니다.

Future가 진행하는 방식

Future는 스스로 실행되지 않습니다. Tokio 런타임과 같은 실행기가 반복해서 이를 "폴링"하여 진행되었는지 또는 완료되었는지 확인합니다.

  • 폴링되면 Future는 Poll::Pending(아직 완료되지 않음) 또는 Poll::Ready(T)(완료됨, 결과는 다음과 같음) 중 하나를 반환합니다.
  • Pending이면 실행기는 관련 이벤트가 발생한 뒤(예: 입출력 완료) 다시 폴링해야 한다는 것을 알 수 있습니다.
  • 이 폴링 메커니즘 덕분에 하나의 스레드에서 많은 비동기 작업을 동시에 실행할 수 있습니다.

여러분의 async 코드는 Future입니다

async fn을 작성하면 Rust는 이를 Future 트레이트를 구현하는 상태 머신으로 변환합니다. 실제 계산은 반환된 Future가 폴링될 때만 시작됩니다.

간단한 async 함수를 살펴보겠습니다. 이 함수는 실행기에 의해 기다려질 때까지 실행되지 않습니다.

async fn say_hello() -> String {
    "Hello from a Future!".to_string()
}

#[tokio::main]
async fn main() {
    let future = say_hello(); // This doesn't run the function yet!
    println!("Future created, but not awaited.");
    // To run it, an executor needs to poll it, often via .await
    // let result = future.await;
    // println!("{}", result);
}

Future가 작업이 되는 과정

Future가 비동기 계산의 정의라면, 작업은 실행기가 해당 Future를 완료까지 진행시키는 활성 인스턴스입니다.

  • Tokio와 같은 실행기에 Future를 실행하라고 요청하면 실행기는 이를 작업으로 감쌉니다.
  • 그런 다음 실행기는 작업이 진행할 준비가 될 때마다 폴링하며 작업을 관리합니다.
  • 작업은 비동기 런타임이 예약하고 동시에 실행하는 작업 단위입니다.

동시 실행을 위한 작업 생성

Future를 다른 코드와 동시에 실행하려면 Tokio 런타임에 이를 "생성"해야 합니다. 그러면 런타임이 관리할 새 작업이 만들어집니다.

tokio::spawn 함수는 Future를 받아 JoinHandle을 반환합니다. 이 핸들을 사용하면 작업의 완료를 기다리고 결과를 얻을 수 있습니다.

async fn perform_task(id: u8) -> String {
    tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
    format!("Task {} finished!", id)
}

#[tokio::main]
async fn main() {
    println!("Main started.");
    let handle = tokio::spawn(async {
        perform_task(1).await
    });
    println!("Task 1 spawned.");
    // We will await 'handle' in the next scene to get the result.
    // let result = handle.await.unwrap();
    // println!("{}", result);
}

작업에서 결과 가져오기

tokio::spawn이 반환하는 JoinHandle 자체도 Future입니다. 이 핸들을 .await하면 생성된 작업이 완료될 때까지 기다린 후 반환값을 가져올 수 있습니다.

생성된 작업이 패닉을 일으키면 해당 JoinHandle을 기다린 결과로 오류가 반환됩니다.

async fn perform_task(id: u8) -> String {
    tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
    format!("Task {} finished!", id)
}

#[tokio::main]
async fn main() {
    println!("Main started.");
    let handle = tokio::spawn(async {
        perform_task(1).await
    });
    println!("Task 1 spawned.");
    let result = handle.await.unwrap(); // Await the handle to get the result
    println!("{}", result);
    println!("Main finished.");
}

여러 동시 작업

여러 작업과 tokio::spawn을 사용해 여러 작업을 동시에 실행할 때 그 강력한 기능이 진정으로 드러납니다. Tokio 런타임은 작업이 실행할 준비가 될 때마다 작업 사이를 효율적으로 전환합니다.

따라서 하나의 스레드에서도 실행을 차단하지 않고 여러 작업을 한꺼번에 진행할 수 있습니다.

async fn long_task(id: u8) -> String {
    tokio::time::sleep(tokio::time::Duration::from_millis(100 + id as u64 * 50)).await;
    format!("Long task {} done!", id)
}

#[tokio::main]
async fn main() {
    println!("Main started.");
    let handle1 = tokio::spawn(long_task(1));
    let handle2 = tokio::spawn(long_task(2));
    println!("Both tasks spawned.");
    let result1 = handle1.await.unwrap();
    let result2 = handle2.await.unwrap();
    println!("{}\n{}", result1, result2);
    println!("Main finished.");
}

join!으로 모두 기다리기

여러 Future가 동시에 완료되기를 기다려야 할 때 tokio::join!은 유용한 매크로입니다. 주어진 모든 퓨처를 동시에 기다린 다음 그 결과를 튜플로 반환합니다.

각 핸들을 개별적으로 기다리는 것과 비슷하지만, 퓨처의 개수가 정해져 있을 때 더 간결한 경우가 많습니다.

async fn fetch_data(source: &str) -> String {
    tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
    format!("Data from {}", source)
}

#[tokio::main]
async fn main() {
    println!("Starting data fetches...");
    let (data_a, data_b) = tokio::join!(
        fetch_data("Server A"),
        fetch_data("Database B")
    );
    println!("Fetched: {}\nFetched: {}", data_a, data_b);
    println!("All fetches complete.");
}

작업 오류 처리

비동기 작업도 동기 작업과 마찬가지로 실패할 수 있습니다. Future가 성공 또는 실패를 나타내는 Result 타입을 반환하는 것은 흔한 방식입니다.

JoinHandle을 기다릴 때는 작업 자체가 패닉을 일으킬 수 있으므로 결과가 또 다른 Result 안에 감싸져 있다는 점을 기억하세요. async fn 내부에서 Result를 반환한다면 handle.await?를, 또는 handle.await.unwrap() / .expect()를 자주 보게 됩니다.

async fn might_fail(should_fail: bool) -> Result<String, &'static str> {
    tokio::time::sleep(tokio::time::Duration::from_millis(30)).await;
    if should_fail {
        Err("Oops, something went wrong!")
    } else {
        Ok("Operation successful!".to_string())
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let handle_ok = tokio::spawn(might_fail(false));
    let handle_err = tokio::spawn(might_fail(true));

    let result_ok = handle_ok.await??; // Await handle, then await inner Result
    println!("{}", result_ok);

    match handle_err.await? { // Await handle, then handle inner Result
        Ok(val) => println!("{}", val),
        Err(e) => eprintln!("Error: {}", e),
    }
    Ok(())
}

Future와 작업 이해하기

계산으로서의 Future와 실행 인스턴스로서의 작업을 배웠습니다. tokio::spawn과 JoinHandle에 대한 설명 중 옳은 것은 무엇일까요?

복습: Future와 작업

훌륭합니다! 이제 Rust 비동기 생태계의 Future와 작업에 관한 핵심 개념을 이해하셨습니다.

  • Future는 언젠가 값을 생성할 비동기 계산을 나타내는 트레이트입니다.
  • async fn은 Future 트레이트를 구현하는 타입으로 컴파일됩니다.
  • 작업은 실행기(Tokio 등)가 적극적으로 관리하고 폴링하는 Future의 인스턴스입니다.
  • tokio::spawn은 새 작업을 만들며 JoinHandle을 반환합니다.
  • 생성된 작업의 결과를 얻으려면 JoinHandle을 .await합니다.

이 구성 요소들은 효율적이고 비차단 방식의 Rust 애플리케이션을 작성하는 데 필수적입니다!

자주 묻는 질문

“퓨처와 작업 다루기” 강의는 무료인가요?

네 — “퓨처와 작업 다루기” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 Learn Rust Coding 강의 전체를 잠금 해제할 수 있습니다. Learn Rust Coding 강의에는 총 3개의 강의가 포함되어 있습니다.

“퓨처와 작업 다루기”에서 뭘 배우나요?

`Future` 트레이트와 비동기 런타임 환경에서 작업이 예약되고 관리되는 방식을 이해합니다. 브라우저에서 직접 실행하는 실습 코드로 Learn Rust Coding을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

Learn Rust Coding을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 Learn Rust Coding은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 3개 중 3번째 강의입니다.

“퓨처와 작업 다루기” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 Learn Rust Coding 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 Learn Rust Coding 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. Async/Await 입문
  2. Tokio로 비동기 애플리케이션 만들기
  3. 퓨처와 작업 다루기
← Learn Rust Coding(으)로 돌아가기