🦀 Асинхронность в Rust: что такое async / await и как это работает


На главную > Блог > Категория > 🦀 Асинхронность в Rust: что такое async / await и как это работает

asynchrony_rust

Вступление: почему трейдеру нужно понимать асинхронность

Вы когда-нибудь задумывались, почему ваш торговый бот на Python может «подвиснуть» во время ожидания ответа от биржи? Или почему он обрабатывает WebSocket-события по одному, хотя данные приходят пачками? Это проблема синхронного кода: программа блокируется на каждой операции ввода-вывода (сеть, диск, таймер).

Rust предлагает элегантное решение — асинхронное программирование с ключевыми словами async и await. В этой статье я объясню, что такое асинхронность, зачем она нужна в трейдинге, и как использовать async/await в Rust без страха и боли.

«Асинхронный код — это как официант в ресторане. Пока он ждёт, пока повар готовит блюдо, он уже обслуживает другой столик. Синхронный код — это как единственный официант, который стоит и смотрит, как готовят, ничего не делая».

1. Что такое асинхронность и зачем она нужна в трейдинге

Синхронный код (плохо для трейдинга):


fn main() {
    let price = get_price_from_binance(); // блокируем поток на 200 мс
    let rsi = calculate_rsi(price);       // блокируем ещё на 50 мс
    send_order(rsi);                      // блокируем на 100 мс
}

Программа простаивает 350 мс, за которые можно было бы обработать ещё 10 тиков с биржи. В трейдинге это критично — вы теряете преимущество.

Асинхронный код (хорошо):


async fn main() {
    let price = get_price_from_binance().await; // не блокирует поток
    let rsi = calculate_rsi(price).await;       // не блокирует
    send_order(rsi).await;                      // не блокирует
}

Пока одна задача ждёт ответа от сети, рантайм переключается на другую задачу. Поток не простаивает.

📊 Для трейдера это значит:
  • ✅ Обработка WebSocket-потока в реальном времени.
  • ✅ Параллельный запрос данных с нескольких бирж.
  • ✅ Расчёт индикаторов и отправка ордеров без блокировки.
  • ✅ Мгновенная реакция на стоп-лоссы и тейк-профиты.

2. Основа асинхронности в Rust: Future

В Rust асинхронные функции возвращают тип Future — это ленивое вычисление, которое ещё не запущено. Future можно выполнить, только дождавшись его (await) или передав в executor.


use std::future::Future;

// Асинхронная функция (возвращает Future)
async fn fetch_price() -> f64 {
    // имитация запроса к бирже
    tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
    67000.0
}

fn main() {
    let future = fetch_price(); // Future не выполнен, просто объект
    // Чтобы выполнить, нужен executor
}

Будущее (Future) в Rust ленивое — оно ничего не делает, пока его не опросят. Это отличает Rust от JavaScript, где async функции стартуют сразу.

3. Runtime (Tokio) — двигатель асинхронности

Чтобы выполнить Future, нужен асинхронный рантайм. Самый популярный в Rust — Tokio. Он управляет очередью задач, потоками и таймерами.

Старт асинхронной программы с Tokio:

#[tokio::main]
async fn main() {
    let price = fetch_price().await;
    println!("Цена: {}", price);
}

Макрос #[tokio::main] создаёт асинхронный рантайм и запускает в нём вашу main функцию.

⚡ В чём отличие от потоков (std::thread)?
  • Потоки создаются системой, каждая задача требует 1-2 МБ памяти.
  • Асинхронные задачи (Future) весят байты, можно создавать миллионы.
  • Потоки переключаются планировщиком ОС (дорого), а задачи — в пользовательском пространстве (быстро).

4. async / await на практике: работа с WebSocket Binance

Напишем простой трейдерский бот, который подключается к WebSocket Binance и обрабатывает тики асинхронно.

Добавляем зависимости в Cargo.toml:


[dependencies]
tokio = { version = "1", features = ["full"] }
tokio-tungstenite = "0.20"
futures-util = "0.3"
serde_json = "1"

Код бота:


use tokio_tungstenite::{connect_async, tungstenite::protocol::Message};
use futures_util::{SinkExt, StreamExt};
use serde_json::Value;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Подключаемся к WebSocket Binance
    let url = "wss://stream.binance.com:9443/ws/btcusdt@trade";
    let (ws_stream, _) = connect_async(url).await?;
    let (mut write, mut read) = ws_stream.split();
    
    println!("✅ WebSocket подключён");
    
    // Обрабатываем входящие сообщения
    while let Some(msg) = read.next().await {
        let msg = msg?;
        if msg.is_text() {
            let text = msg.to_text()?;
            let json: Value = serde_json::from_str(text)?;
            
            if let (Some(price), Some(volume)) = (json.get("p"), json.get("q")) {
                let price_f64 = price.as_str().unwrap().parse::()?;
                let volume_f64 = volume.as_str().unwrap().parse::()?;
                
                println!("💰 BTC/USDT: {:.2}, объем: {:.2}", price_f64, volume_f64);
                
                // Здесь можно вызвать стратегию
                process_trade(price_f64, volume_f64).await;
            }
        }
    }
    
    Ok(())
}

async fn process_trade(price: f64, volume: f64) {
    // Симуляция расчёта индикатора (не блокирует, т.к. async)
    tokio::time::sleep(tokio::time::Duration::from_micros(10)).await;
    
    if price > 68000.0 && volume > 10.0 {
        println!("📈 Сигнал на покупку!");
        // Отправка ордера тоже асинхронная
        send_order("BUY", 0.01).await;
    }
}

async fn send_order(side: &str, quantity: f64) {
    // Имитация запроса к API биржи
    tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
    println!("✅ Ордер {} {} отправлен", side, quantity);
}
🎯 Почему это круто: Бот не блокируется на ожидании ответа от биржи. Пока один тик обрабатывается, рантайм уже взял следующий. Асинхронность — ключ к real-time приложениям.

5. Обработка ошибок и тайм-ауты

В асинхронном коде ошибки нужно обрабатывать так же, как в синхронном — с помощью Result и ?.

Тайм-аут для операции (чтобы бот не висел вечно):


use tokio::time::timeout;
use std::time::Duration;

async fn fetch_price_with_timeout() -> Result<f64, Box<dyn std::error::Error>> {
    let result = timeout(Duration::from_secs(2), fetch_price()).await;
    
    match result {
        Ok(price) => Ok(price),
        Err(_) => {
            eprintln!("❌ Тайм-аут: биржа не ответила за 2 секунды");
            Ok(0.0)
        }
    }
}
⚠️ Важно: Асинхронный код не спасает от ошибок. Всегда обрабатывайте тайм-ауты и сетевые ошибки, иначе бот может «зависнуть» навсегда.

6. Параллельная обработка нескольких инструментов (join!)

Асинхронность позволяет одновременно следить за BTC, ETH и SOL. Для этого используется tokio::join!.

async fn watch_ticker(symbol: &str) {
    let url = format!("wss://stream.binance.com:9443/ws/{}@trade", symbol.to_lowercase());
    // ... логика подключения
    loop {
        // обрабатываем тики
        tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
    }
}

#[tokio::main]
async fn main() {
    let btc = tokio::spawn(watch_ticker("BTCUSDT"));
    let eth = tokio::spawn(watch_ticker("ETHUSDT"));
    let sol = tokio::spawn(watch_ticker("SOLUSDT"));
    
    // Ждём выполнения всех задач (параллельно)
    let _ = tokio::join!(btc, eth, sol);
}

7. Подводные камни async/await в Rust

7.1. Отсутствие блокировок (`.await` не блокирует поток)

Только .await даёт рантайму переключиться на другую задачу. Если внутри асинхронной функции вызвать синхронную блокирующую операцию (например, std::thread::sleep), заблокируется весь поток рантайма. Используйте tokio::time::sleep.


// Плохо (блокирует поток)
std::thread::sleep(Duration::from_secs(1));

// Хорошо (не блокирует)
tokio::time::sleep(Duration::from_secs(1)).await;

7.2. Send-границы и многопоточность

Если вы запускаете асинхронные задачи на многопоточном рантайме, все данные, передаваемые между задачами, должны реализовывать трейт Send. Rc нельзя, используйте Arc.

7.3. Жизнь async-функций

Асинхронные функции не могут захватывать ссылки через await — используйте Arc или клонируйте данные.

8. Заключение: асинхронность — ключ к real-time трейдингу

Асинхронное программирование с async/await в Rust позволяет создавать высокопроизводительные, отзывчивые и безопасные торговые системы. Вы можете одновременно следить за сотней инструментов, обрабатывать WebSocket-события и отправлять ордера, не блокируя потоки.

Tokio предоставляет всё необходимое: таймеры, сети, файловый ввод-вывод, мьютексы и каналы для асинхронного кода. А строгая система типов Rust не даст вам сделать распространённые ошибки (например, забыть await).

Начните с малого: подключитесь к WebSocket Binance, обрабатывайте тики, отправляйте их в канал. Затем добавьте стратегию на основе скользящих средних. Потом — отправку ордеров. За выходные вы сможете написать бота, который работает быстрее, чем его аналог на Python, и не «зависает» при просадке сети.

И помните: асинхронность — это не магия, а инструмент. Он требует понимания, но открывает возможности, недоступные синхронному коду. В мире HFT и real-тайм трейдинга асинхронность — это не роскошь, а необходимость.

«Асинхронный Rust — как швейцарские часы. Каждая деталь на своём месте, и весь механизм работает без задержек. А Tokio — это мастер, который собирает эти часы».

 

Дата размещения статьи: 09-06-2026 в 09:08:16