Работа с будущими и задачами
Разберитесь в трейте `Future`, а также в том, как задачи планируются и управляются в асинхронной среде выполнения.
«Работа с будущими и задачами» — бесплатный урок Learn Rust Coding на CoddyKit. Это урок 3 из 3. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Learn Rust Coding, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Learn Rust Coding содержит 3 уроков всего.
Что такое Future в Rust
В асинхронном Rust Future — это трейт, представляющий асинхронное вычисление, которое может завершиться в некоторый момент. Представляйте его как обещание значения, которое ещё не готово.
- Это основной строительный блок асинхронного Rust.
- Функции
async fnв Rust фактически возвращают анонимный тип, реализующий трейтFuture. - Значение вычисляется не сразу, а в момент, когда исполнитель выполняет «опрос» (poll)
Future.
Как фьючерсы продвигаются к завершению
Future не выполняется самостоятельно. Исполнитель (например, среда Tokio) многократно выполняет его «опрос», проверяя, продвинулось ли вычисление или завершилось ли оно.
- При опросе
Futureвозвращает либоPoll::Pending(ещё не завершено), либоPoll::Ready(T)(завершено, вот результат). - Если возвращено
Pending, исполнитель знает, что нужно опросить его снова позднее, когда произойдёт что-то важное (например, завершится ввод-вывод). - Именно этот механизм опроса позволяет множеству асинхронных операций выполняться одновременно в одном потоке.
Ваш асинхронный код — это 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, выполнение которого исполнитель доводит до завершения.
- Когда Вы поручаете исполнителю (например, 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, поскольку сама задача может завершиться паникой. Часто встречаются handle.await? (внутри async fn, возвращающей Result) или 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(())
}Понимание фьючерсов и задач
Вы узнали о фьючерсах как о вычислениях и о задачах как об экземплярах, выполняющих эти вычисления. Какое утверждение о tokio::spawn и JoinHandle верно?
Итоги: фьючерсы и задачи
Отличная работа! Теперь Вы освоили основные понятия Future и задач в асинхронной экосистеме Rust:
Future— это трейт, представляющий асинхронное вычисление, которое со временем выдаст значение.- Функции
async fnкомпилируются в типы, реализующие трейтFuture. - Задача — это экземпляр
Future, которым исполнитель (например, Tokio) активно управляет и который опрашивает. tokio::spawnиспользуется для создания новой задачи и возвращаетJoinHandle.- С помощью
.awaitдляJoinHandleможно получить результат запущенной задачи.
Эти строительные блоки необходимы для написания эффективных неблокирующих приложений на Rust!
Часто задаваемые вопросы
Урок «Работа с будущими и задачами» бесплатный?
Да — полный текст урока «Работа с будущими и задачами» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Learn Rust Coding, подпишись на CoddyKit PRO. Курс Learn Rust Coding содержит 3 уроков всего.
Чему я научусь в уроке «Работа с будущими и задачами»?
Разберитесь в трейте `Future`, а также в том, как задачи планируются и управляются в асинхронной среде выполнения. Ты практикуешь Learn Rust Coding с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Learn Rust Coding?
Предыдущий опыт не требуется. Learn Rust Coding на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 3.
Сколько времени занимает урок «Работа с будущими и задачами»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Learn Rust Coding?
Да. Каждый урок Learn Rust Coding включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Введение в Async/Await
- Создание асинхронных приложений с Tokio
- Работа с будущими и задачами