Модуль 4.3 — Асинхронная многозадачность
Упражнение 4.3.1: От синхронного кода к асинхронному
Синхронный и асинхронный код на Rust выглядят не так уж по-разному. В этом упражнении мы превратим синхронный TCP-эхо-сервер на Rust в асинхронный.
Откройте exercises/4-multitasking/3-asynchronous-multitasking/1-sync-to-async в редакторе. Следуя шагам в main.rs, сначала проверьте, что программа работает, затем преобразуйте её в async и снова проверьте, что она работает.
Упражнение 4.3.2: Приёмник данных измерений
В этом сценарии есть набор IoT-датчиков, которые измеряют качество воздуха в разных комнатах. Они отправляют данные через TCP-сокет на сервер. Сервер агрегирует данные по комнатам и записывает их в CSV-файл. Сейчас эта функциональность реализована синхронно. Ваша задача — сделать серверный код async.
Откройте exercises/4-multitasking/3-asynchronous-multitasking/2-measurement-data-sink в редакторе.
В двух разных терминалах запустите:
cargo run --bin server
и
cargo run --bin sensor-nodes
Вы должны увидеть регулярные лог-сообщения о полученных измерениях. Каждые 60 секунд в database.csv будут добавляться новые строки.
Упражнение 4.3.2A: async-изация
Затем разберитесь с комментариями TODO: в src/bin/server.rs. Проверьте, что приложение по-прежнему работает, как и раньше.
Упражнение 4.3.2B: Изменение требований
Запустите клиентов с интервалом 10 секунд:
cargo run --bin sensor-nodes -- -i 10s
Разберитесь с возникающими ошибками и устраните их.
Упражнение 4.3.3: Асинхронные каналы
Каналы — очень удобный способ обмениваться сообщениями между потоками и async-задачами. Они позволяют разбить приложение на множество слабо связанных задач. Вы увидите, как это пригодится в упражнении E.2. В этом упражнении вы реализуете два варианта: oneshot-канал и канал «много отправителей — один получатель» (multi-producer-single-consumer, MPSC). Если хочется испытать себя, можно написать и broadcast-канал.
4.3.3.A Канал MPSC ⭐⭐
Канал MPSC (multi-producer-single-consumer) позволяет нескольким Sender отправлять много сообщений одному Receiver.
Откройте exercises/4-multitasking/3-asynchronous-multitasking/3-async-channels в редакторе. Там вы найдёте заготовку кода. Для части A работайте в src/mpsc.rs. Исправьте todo! в этом файле, чтобы тест прошёл. Для проверки выполните:
cargo test -- mpsc
Если тесты зависают, скорее всего, ваша реализация неправильно использует Waker или возвращает Poll::Pending там, где не должна.
4.3.3.B Oneshot-канал ⭐⭐⭐
Oneshot — канал, который позволяет одному Sender отправить ровно одно сообщение одному Receiver.
Для части B работайте в src/broadcast.rs. На этот раз больше придётся сделать самостоятельно. Ожидаемое поведение:
ReceiverреализуетFuture. Он возвращаетPoll::Ready(Ok(T)), еслиinner.dataравноSome(T);Poll::Pending, еслиinner.dataравноNone; иPoll::Ready(Err(Error::SenderDropped)), еслиSenderбыл уничтожен.Receiver::pollзаменяетinner.wakerна тот, что пришёл вContext.Senderпотребляетselfпри отправке, поэтому его можно использовать не более одного раза. Отправка устанавливаетinner.dataвSome(T). ЕслиReceiverбыл уничтожен до отправки, возвращаетсяErr(Error::ReceiverDropped(T)).Sender::sendбудитinner.wakerпосле того, как записал данные вinner.data.- Когда
Senderуничтожается, он помечает себя уничтоженным вinner. - Когда
Receiverуничтожается, он помечает себя уничтоженным вinner. - После успешной отправки потреблённый
Senderне помечается как уничтоженный. Вместо этого используетсяstd::mem::forget, чтобы не запускать деструктор.
Для проверки выполните:
cargo test -- broadcast
4.3.3.C Broadcast-канал (бонус) ⭐⭐⭐⭐
Broadcast-канал поддерживает несколько отправителей и несколько получателей. Каждое сообщение, отправленное любым из отправителей, получает каждый получатель. Поэтому реализация должна хранить сообщения, пока их не получит каждый ещё не уничтоженный получатель. Из этого также следует, что при рассылке сообщение нужно клонировать.
В этом бонусном упражнении заготовки нет. Вдохновляйтесь модулями mpsc и oneshot и реализуйте модуль broadcast самостоятельно.