#rust #async-await #rust-tokio
Вопрос:
Приветствую Вас, Растоптанные,
В настоящее время я пытаюсь перенести приложение JVM (точнее, бота discord) в rust для развлечения и обучения, используя serenity-rs, и в настоящее время я сталкиваюсь с кирпичной стеной с задачами tokio и общим состоянием. Я очень новичок во всем этом опыте с ржавчиной, поэтому, пожалуйста, будьте нежны.
Основная идея заключается в том, что какая-то задача запускается асинхронно и после определенного времени ожидания она вставляет некоторые данные в общую (параллельную) карту. Другая часть программы прослушивает события, и если одно из таких событий произойдет, пока первая задача все еще ожидает, оно отменит указанную задачу, что приведет к тому, что данные не будут вставлены в общую карту.
Я начал с «упрощенного» примера, который выглядит так и зависит только от токио.
[dependencies]
tokio = { version = "1", features = ["full"] }
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
use tokio::join;
use tokio::sync::RwLock;
use tokio::time;
#[tokio::main]
async fn main() {
let mut data: Arc<RwLock<HashMap<_,_>>> =
Arc::new(RwLock::new(HashMap::new()));
let mut tasks: Arc<RwLock<HashMap<String, _>>> =
Arc::new(RwLock::new(HashMap::new()));
let mut d1 = data.clone();
let handle_a = tokio::spawn(async move {
time::sleep(Duration::from_secs(30)).await;
{
let mut lock = d1.write().await;
lock.insert("foo", "bar");
}
});
let handle_a = Arc::new(handle_a);
{
let mut map = tasks.write().await;
map.insert("the_task".to_string(), handle_a.clone());
}
let mut d2 = data.clone();
let handle_b = tokio::spawn(async move {
tokio::time::sleep(Duration::from_secs(10)).await;
{
let d = d2.read().await;
let res = d.get("foo");
println!("After 10sec: {}", res.unwrap_or(amp;"None"));
let mut m = tasks.write().await;
{
let t = m.get("the_task").unwrap();
println!("Now cancelling: {:?}", t);
t.abort();
let _ = m.remove("the_task");
}
}
tokio::time::sleep(Duration::from_secs(25)).await;
{
let d = d2.read().await;
let res = d.get("foo").unwrap_or(amp;"None");
println!("After 35sec: {}", res)
}
});
join!(handle_a, handle_b);
}
Который в настоящее время выдает следующую ошибку при компиляции:
55 | join!(handle_a, handle_b);
| ^^^^^^^^ `Arc<tokio::task::JoinHandle<()>>` is not a future
Я завернул handle_a
в an Arc
, потому что хочу дождаться его «в конце основной функции», все еще имея возможность поместить ссылку на него в карту задач. Так что это, очевидно, кажется неправильным, но я не могу придумать другого способа справиться с этим. Просто вызов deref()
handle_a
приведет к другой ошибке:
`Future` is implemented for `amp;mut tokio::task::JoinHandle<()>`, but not for `amp;tokio::task::JoinHandle<()>
Что, я думаю, имеет смысл, потому что документы для Arc
штата:
Общие ссылки в Rust по умолчанию запрещают мутацию, и Arc не является исключением: обычно вы не можете получить изменяемую ссылку на что-либо внутри Дуги.
Я думаю, что «основанный на вытягивании» подход фьючерсов в Rust-это то, что действительно заставляет меня бороться здесь, потому что мне нужна ссылка на JoinHandle, чтобы дождаться ее в конце основной функции.
Вероятно, это просто совершенно неправильный подход к этой проблеме, поэтому я был бы очень благодарен за любые намеки или подсказки в правильном направлении.
Уже спасибо за потраченное время, если вы дочитали до этого места!
Правка: Исправлена опечатка, упомянутая в принятом ответе.
Ответ №1:
tokio::spawn
уже выполняет будущее в фоновом режиме. Вам нужно только дождаться его, если вы хотите дождаться его результата, который вам в данном случае не нужен. Вы можете просто заменить его на b.await
.
У вас также есть небольшая логическая ошибка в названии задачи — вы используете "thetask"
вместо "the_task"
одного запроса. Я немного очистил код, и вы можете найти рабочую версию на этой игровой площадке.
Я не знаком с дизайном ботов Discord, но если у вас есть только один «цикл событий», который работает все время и отвечает как за создание, так и за отмену задач, вам не нужно создавать для него задачу tokio::spawn
, но вместо этого вы можете запустить ее непосредственно в main
функции. Это означало бы, что он может владеть tasks
картой исключительно, и у вас не возникнет проблем с совместным владением.
Комментарии:
1. Спасибо за указание на то, что для запуска задач tokio не нужно ждать. Похоже, я неправильно понял это, просматривая документы. Тем временем я также нашел решение, использующее асинхронные fn с
Abortable
AbortHandle
ящиком фьючерсов и из него, но решение задачи tokio кажется намного проще. К сожалению, платформа discord основана на общей карте между вызовами обработчиков, так что мне все равно придется иметь дело с этим, но пока, похоже, она работает нормально.