Вступ до асинхронного програмування в Rust

Традиційні моделі синхронного програмування часто призводять до вузьких місць продуктивності. Це пояснюється тим, що програма очікує завершення повільних операцій, перш ніж перейти до наступного завдання. Це часто призводить до поганого використання ресурсів і млявої взаємодії з користувачем.

Асинхронне програмування дозволяє писати неблокуючий код, який ефективно використовує системні ресурси. Використовуючи асинхронне програмування, ви можете створювати програми, які виконують кілька завдань. Асинхронне програмування зручно для обробки кількох мережевих запитів або обробки великих обсягів даних без блокування потоку виконання.

Асинхронне програмування в Rust

Модель асинхронного програмування Rust дозволяє писати ефективний код Rust, який виконується одночасно, не блокуючи потік виконання. Асинхронне програмування є корисним при роботі з операціями вводу-виводу, мережевими запитами та завданнями, які включають очікування зовнішніх ресурсів.

Ви можете реалізувати асинхронне програмування у своїх програмах Rust кількома способами. До них належать мовні функції, бібліотеки та середовище виконання Tokio.

Крім того, модель власності Rust і примітиви паралелізму, такі як канали та блокування, забезпечують безпечне та ефективне паралельне програмування. Ви можете використовувати ці функції за допомогою асинхронного програмування для створення паралельних систем, які добре масштабуються та використовують кілька ядер ЦП.

Концепції асинхронного програмування Rust

Futures забезпечує основу для асинхронного програмування в Rust. Майбутнє представляє асинхронне обчислення, яке не було повністю виконано.

Ф’ючерси ліниві (вони виконуються лише під час опитування). Коли ви викликаєте метод future poll(), він перевіряє, чи майбутнє завершено чи потребує додаткової роботи. Якщо майбутнє не готове, воно повертає Poll::Pending, вказуючи, що завдання має бути заплановано для подальшого виконання. Якщо майбутнє готове, воно повертає Poll::Ready з результуючим значенням.

Стандартний ланцюжок інструментів Rust включає асинхронні примітиви вводу-виводу, асинхронну версію файлового вводу-виводу, мережу та таймери. Ці примітиви дозволяють виконувати операції вводу-виводу асинхронно. Це допомагає уникнути блокування виконання програми під час очікування завершення завдань введення-виведення.

Синтаксис async/await дозволяє писати асинхронний код, схожий на синхронний код. Це робить ваш код інтуїтивно зрозумілим і простим у обслуговуванні.

Підхід Rust до асинхронного програмування наголошує на безпеці та продуктивності. Правила володіння та запозичення забезпечують безпеку пам’яті та запобігають поширеним проблемам паралелізму. Синтаксис Async/await і ф’ючерси забезпечують інтуїтивно зрозумілий спосіб вираження асинхронних робочих процесів. Ви можете використовувати стороннє середовище виконання, щоб керувати завданнями для ефективного виконання.

Ви можете комбінувати ці мовні функції, бібліотеки та середовище виконання, щоб написати високопродуктивний код. Він забезпечує потужну та ергономічну структуру для побудови асинхронних систем. Це робить Rust популярним вибором для проектів, які вимагають ефективної обробки завдань, пов’язаних із введенням/виведенням, і високого паралелізму.

Rust версії 1.39 і пізніших випусків не підтримують асинхронні операції в стандартній бібліотеці Rust. Щоб використовувати синтаксис async/await для обробки асинхронних операцій у Rust, вам знадобиться сторонній ящик. Для роботи з синтаксисом async/await можна використовувати пакети сторонніх розробників, такі як Tokio або async-std.

Асинхронне програмування з Tokio

Tokio — це надійне асинхронне середовище виконання для Rust. Він забезпечує функціональність для створення високопродуктивних і масштабованих програм. Ви можете використовувати потужність асинхронного програмування за допомогою Tokio. Він також надає функції для розширення.

В основі Tokio лежить модель асинхронного планування та виконання завдань. Tokio дозволяє писати асинхронний код із синтаксисом async/await. Це забезпечує ефективне використання системних ресурсів і одночасне виконання завдань. Цикл подій Tokio ефективно керує плануванням завдань. Це забезпечує оптимальне використання ядер ЦП і мінімізує накладні витрати на перемикання контексту.

Комбінатори Tokio спрощують координацію та композицію завдань. Tokio надає потужні інструменти для координації завдань і композиції. Ви можете дочекатися завершення кількох завдань за допомогою об’єднання, вибрати перше виконане завдання за допомогою вибору та змагатися між собою за допомогою гонки.

Додайте ящик tokio до розділу залежностей вашого файлу Cargo.toml.

 [dependencies]
tokio = { version = "1.9", features = ["full"] }

Ось як ви можете використовувати синтаксис async/await у своїх програмах Rust із Tokio:

 use tokio::time::sleep;
use std::time::Duration;

async fn hello_world() {
    println!("Hello, ");
    sleep(Duration::from_secs(1)).await;
    println!("World!");
}

#[tokio::main]
async fn main() {
    hello_world().await;
}

Функція hello_world є асинхронною, тому вона може використовувати ключове слово await, щоб призупинити своє виконання, доки не буде вирішено майбутнє. Функція hello_world друкує «Hello,» на консоль. Виклик функції Duration::from_secs(1) призупиняє виконання функції на секунду. Ключове слово await очікує завершення майбутнього сну. Нарешті, функція hello_world друкує «World!» до консолі.

Основна функція — це асинхронна функція з #[tokio::main] атрибут. Він визначає основну функцію як точку входу для середовища виконання Tokio. Функція hello_world().await виконує функцію hello_world асинхронно.

Відкладення завдань із Tokio

Поширеним завданням в асинхронному програмуванні є використання затримок або планування завдань для виконання в заданому часовому діапазоні. Середа виконання tokio забезпечує механізм використання асинхронних таймерів і затримок через модуль tokio::time.

Ось як ви можете відкласти операцію за допомогою середовища виконання Tokio:

 use std::time::Duration;
use tokio::time::sleep;

async fn delayed_operation() {
    println!("Performing delayed operation...");
    sleep(Duration::from_secs(2)).await;
    println!("Delayed operation completed.");
}

#[tokio::main]
async fn main() {
    println!("Starting...");
    delayed_operation().await;
    println!("Finished.");
}

Функція delayed_operation вводить затримку в дві секунди з методом сну. Функція delayed_operation є асинхронною, тому вона може використовувати await, щоб призупинити її виконання, доки затримка не завершиться.

Обробка помилок в асинхронних програмах

Обробка помилок в асинхронному коді Rust передбачає використання типу Result і обробку помилок Rust за допомогою ? оператор.

 use tokio::fs::File;
use tokio::io;
use tokio::io::{AsyncReadExt};

async fn read_file_contents() -> io::Result<String> {
    let mut file = File::open("file.txt").await?;
    let mut contents = String::new();
    file.read_to_string(&mut contents).await?;
    Ok(contents)
}

async fn process_file() -> io::Result<()> {
    let contents = read_file_contents().await?;
    
    Ok(())
}

#[tokio::main]
async fn main() {
    match process_file().await {
        Ok(()) => println!("File processed successfully."),
        Err(err) => eprintln!("Error processing file: {}", err),
    }
}

Функція read_file_contents повертає io::Result, який представляє можливість помилки введення-виведення. Використовуючи ? після кожної асинхронної операції середовище виконання Tokio поширюватиме помилки вгору по стеку викликів.

Основна функція обробляє результат за допомогою оператора відповідності, який друкує текст на основі результату операції.

Reqwest використовує асинхронне програмування для операцій HTTP

Багато популярних ящиків, включаючи Reqwest, використовують Tokio для забезпечення асинхронних операцій HTTP.

Ви можете використовувати Tokio з Reqwest, щоб зробити кілька HTTP-запитів, не блокуючи інші завдання. Tokio може допомогти вам обробляти тисячі одночасних підключень і ефективно керувати ресурсами.