AWAIT vs SPAWN

1. await - приостановка выполнения задачи

Что делает:

await приостанавливает выполнение текущей асинхронной функции до тех пор, пока будущее (Future) не будет завершено. При этом он не блокирует весь поток - пока одна задача ждет, другие могут выполняться.

Простой пример:

use tokio::time::{sleep, Duration};

async fn download_file(filename: &str) -> String {
    println!("Начинаю загрузку: {}", filename);
    sleep(Duration::from_secs(2)).await; // ⏸️ ПАУЗА здесь
    println!("Завершил загрузку: {}", filename);
    format!("Содержимое {}", filename)
}

#[tokio::main]
async fn main() {
    println!("Начало программы");
    
    let content = download_file("document.txt").await;
    
    println!("Получили: {}", content);
    println!("Конец программы");
}

Вывод:

Начало программы
Начинаю загрузку: document.txt
(ждем 2 секунды...)
Завершил загрузку: document.txt
Получили: Содержимое document.txt
Конец программы

2. tokio::spawn - запуск параллельной задачи

Что делает:

tokio::spawn создает новую асинхронную задачу, которая выполняется параллельно с текущей. Возвращает JoinHandle, через который можно дождаться результата.

Простой пример:

use tokio::time::{sleep, Duration};

async fn download_file(filename: &str) -> String {
    println!("Начинаю загрузку: {}", filename);
    sleep(Duration::from_secs(2)).await;
    println!("Завершил загрузку: {}", filename);
    format!("Содержимое {}", filename)
}

#[tokio::main]
async fn main() {
    println!("Начало программы");
    
    // Запускаем две загрузки параллельно
    let task1 = tokio::spawn(async {
        download_file("document1.txt").await
    });
    
    let task2 = tokio::spawn(async {
        download_file("document2.txt").await
    });
    
    // Ждем завершения обеих задач
    let result1 = task1.await.unwrap();
    let result2 = task2.await.unwrap();
    
    println!("Результаты: {} и {}", result1, result2);
    println!("Конец программы");
}

Вывод (обратите внимание на параллельность):

Начало программы
Начинаю загрузку: document1.txt
Начинаю загрузку: document2.txt
(ждем 2 секунды, но обе загрузки идут параллельно!)
Завершил загрузку: document1.txt
Завершил загрузку: document2.txt
Результаты: Содержимое document1.txt и Содержимое document2.txt
Конец программы

Ключевые различия

Аспектawaittokio::spawn
ПараллелизмПоследовательное выполнениеПараллельное выполнение
БлокировкаНе блокирует поток, только текущую задачуСоздает новую независимую задачу
ИспользованиеДля ожидания результата FutureДля запуска параллельной работы
РезультатЗначение FutureJoinHandle для ожидания результата

Схема работы await (последовательное выполнение):
AwaitSequencecluster_awaitБлокировка awaitAНачало mainBВызов download_file с awaitA->BCawait приостанавливает выполнение mainB->CDФункция download_file выполняетсяC->DEsleep завершен?D->EE->EНетFdownload_file завершенаE->FДаGПродолжается выполнение mainF->GHКонец программыG->H

Схема работы tokio::spawn (параллельное выполнение):
TokioSpawnAНачало mainBtokio::spawn task1A->BCtokio::spawn task2B->CDmain ожидает результатыC->DEtask1: download_file document1.txtD->EFtask2: download_file document2.txtD->FGtask1 завершенаE->GHtask2 завершенаF->HImain продолжаетсяG->IH->IJКонец программыI->J

Комбинированный пример

use tokio::time::{sleep, Duration};

async fn expensive_calculation(n: u32) -> u32 {
    println!("Начинаю вычисление {}", n);
    sleep(Duration::from_secs(1)).await;
    println!("Завершил вычисление {}", n);
    n * n
}

#[tokio::main]
async fn main() {
    // ПЛОХО: последовательное выполнение (3 секунды)
    let start = std::time::Instant::now();
    let _a = expensive_calculation(1).await;
    let _b = expensive_calculation(2).await;
    let _c = expensive_calculation(3).await;
    println!("Последовательное выполнение: {:?}", start.elapsed());
    
    // ХОРОШО: параллельное выполнение (~1 секунда)
    let start = std::time::Instant::now();
    let task1 = tokio::spawn(expensive_calculation(1));
    let task2 = tokio::spawn(expensive_calculation(2));
    let task3 = tokio::spawn(expensive_calculation(3));
    
    // await для каждого JoinHandle
    let _a = task1.await.unwrap();
    let _b = task2.await.unwrap();
    let _c = task3.await.unwrap();
    println!("Параллельное выполнение: {:?}", start.elapsed());
}

Итог

  • await = "Подожди, пока эта операция завершится, прежде чем продолжать"
  • tokio::spawn = "Запусти эту работу в фоне, я могу заниматься другими делами"

Используйте await для последовательных операций, которые зависят друг от друга, и tokio::spawn для независимых операций, которые можно выполнять параллельно.