본문 바로가기
C.W.K.
Stream
Lesson 05 of 07 · published

Async command & 런타임

~14 min · tauri, async, rust, performance

Level 0웹 관광객
0 XP0/56 lessons0/13 achievements
0/100 XP to next level100 XP to go0% complete
"느린 동기 command는 앱을 얼려버려. async는 작업이 도는 동안에도 창이 계속 숨 쉬게 해줘."

async만 붙이면 돼

command를 비동기로 만들려면 async fn으로 쓰면 끝이야. Tauri가 그걸 async 런타임(Tokio)에서 돌리고, 프론트엔드의 invoke Promise는 future가 끝날 때 resolve돼. 이게 중요한 이유는, 오래 걸리는 동기 command가 스레드를 붙잡고 있으면 UI가 얼어붙은 것처럼 보이기 때문이야. 네트워크 호출이든, 자식 프로세스를 기다리는 일이든, I/O로 시간을 보내는 일이든 — async로 만들면 앱이 계속 반응해.

I/O 바운드랑 CPU 바운드

여기 진짜 갈림길이 있어. 기다리는 게 일인 I/O 바운드 작업(HTTP, 디스크, 대기)은 async랑 딱 맞아 — 런타임이 기다리는 태스크를 잠깐 세워두고 다른 걸 돌리거든. 계산이 일인 CPU 바운드 작업(거대한 이미지 리사이즈, 해싱, 무거운 파싱)은 async만으론 얻는 게 없어. async 태스크 안에서 CPU를 계속 돌리면 런타임 자체가 멈춰 설 수 있어. 무거운 계산은 tokio::task::spawn_blocking(아니면 그냥 std::thread)으로 블로킹 스레드에 넘겨서 async 런타임을 풀어줘.

Send 함정

컴파일러가 강제하는 규칙이 하나 있어. async command에서 await넘어가며 붙잡고 있는 데이터는 스레드 사이를 옮겨도 안전(Send)해야 해. 여기서 제일 흔히 걸리는 게 std::sync::Mutex 가드를 await 지점 너머까지 쥐고 있는 거야 — 컴파일이 안 돼. 해결책은 await 전에 락을 놓거나 async를 아는 락을 쓰는 거고. state 트랙에서 제대로 만날 테니 지금은 이것만 기억해. 'cannot be sent between threads safely'는 보통 'await를 넘어가며 락을 쥐고 있었다'는 뜻이야.

Code

async command (I/O 바운드)·rust
// I/O 바운드: 네트워크 호출이 도는 동안 async가 UI 반응성을 유지.
#[tauri::command]
async fn fetch_title(url: String) -> Result<String, String> {
    let body = reqwest::get(&url)
        .await
        .map_err(|e| e.to_string())?
        .text()
        .await
        .map_err(|e| e.to_string())?;
    Ok(body.chars().take(80).collect())
}
spawn_blocking으로 CPU 작업 떠넘기기·rust
// CPU 바운드: async 태스크 안에서 갈지 마 — 떠넘겨.
#[tauri::command]
async fn heavy_hash(data: Vec<u8>) -> Result<String, String> {
    let digest = tokio::task::spawn_blocking(move || {
        expensive_pure_cpu_hash(&data) // 블로킹 스레드 풀에서 실행
    })
    .await
    .map_err(|e| e.to_string())?;
    Ok(digest)
}

External links

Exercise

command 하나를 async로 바꿔봐. 제일 쉬운 시연은 tokio sleep으로 2초를 기다렸다가 문자열을 돌려주는 async command야. 프론트엔드에서 부르고, 도는 동안 UI 나머지(스피너, 카운터)가 계속 움직이는지 확인해. 그다음 같은 지연을 std::thread::sleep으로 동기로 바꿔서 차이를 느껴봐 — 동기 버전은 창을 얼려버려.
Hint
Async는 tokio::time::sleep(Duration::from_secs(2)).await를 써. 어는 걸 느껴보려면 async가 아닌 command 안에서 std::thread::sleep(...)을 직접 불러. 얼어붙은 창이 곧 레슨이야.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.