Я хочу сохранить некоторые операции aysnc в хэш-карте. Затем получите список асинхронных операций на основе списка строк, вводимых пользователем. Поискав по сайту, я нашел этот вопрос
Как я могу поместить асинхронную функцию в карту в Rust?
Разница в том, что я хочу использовать хэш-карту хранимой операции в разных потоках Tokio. Поэтому я переписываю с помощью Once_cell + RwLock.
static MAPS: Lazy<Arc<RwLock<HashMap<&'static str, CalcFn>>>> = Lazy::new(|| {
let map = Arc::new(RwLock::new(HashMap::new()));
let mut map_lock = map.write().unwrap();
map_lock.insert(
"add",
Box::new(
move |a: i32, b: i32| -> Pin<Box<dyn Future<Output = BoxedResult<i32>>>> {
Box::pin(add(a, b))
},
) as CalcFn,
);
map_lock.insert(
"sub",
Box::new(
move |a: i32, b: i32| -> Pin<Box<dyn Future<Output = BoxedResult<i32>>>> {
Box::pin(sub(a, b))
},
) as CalcFn,
);
map.clone()
});
Ленивая инициализация кажется скучной и занимает много строк кода, поскольку у меня может быть более 20 асинхронных операций.
Может ли кто-нибудь любезно предложить несколько комментариев, чтобы написать этот пример «более чистым» способом?
да. Я знаю, что функция или макроправило могут помочь. На самом деле, я ищу, есть ли другой способ или структура данных, также достигающая той же цели.
Первое, что нужно сделать, — это максимально перейти от операции вставки к другой функции.
fn insert_async_fn<K, F, Fut>(map: &mut HashMap<K, CalcFn>, name: K, f: F)
where
K: Eq + Hash,
F: Fn(i32, i32) -> Fut + Send + Sync + 'static,
Fut: Future<Output = BoxedResult<i32>> + Send + 'static,
{
map.insert(name, Box::new(move |a, b| Box::pin(f(a, b))));
}
// Used like this
insert_async_fn(&mut map, "add", add);
insert_async_fn(&mut map, "sub", sub);
Вам не нужно использовать clone
в конце. Все, что вам нужно сделать, это снять замок перед возвращением.
drop(map_lock);
map
Вам не нужен Arc
. Arc
можно использовать, чтобы гарантировать сохранение значения в нескольких потоках, но static
уже гарантирует это.
В опубликованном вами коде RwLock
также не нужен, но он может понадобиться, если вы изменяете карту после построения.
Если вы используете последнюю стабильную версию Rust, вы можете использовать LazyLock из стандартной библиотеки. Он работает так же, как и тот, что из once_cell
.
Обычно вам не нужно, чтобы ошибки были Sync
, но вам часто нужно, чтобы фьючерсы были Send
, чтобы их можно было создавать в многопоточных средах выполнения.
Это оставляет нам следующее:
static MAPS: LazyLock<RwLock<HashMap<&'static str, CalcFn>>> = LazyLock::new(|| {
let mut map = HashMap::new();
insert_async_fn(&mut map, "add", add);
insert_async_fn(&mut map, "sub", sub);
RwLock::new(map)
});
fn insert_async_fn<K, F, Fut>(map: &mut HashMap<K, CalcFn>, name: K, f: F)
where
K: Eq + Hash,
F: Fn(i32, i32) -> Fut + Send + Sync + 'static,
Fut: Future<Output = BoxedResult<i32>> + Send + 'static,
{
map.insert(name, Box::new(move |a, b| Box::pin(f(a, b))));
}
У меня возникла еще одна проблема при использовании tokio::spawn на основе вашего примера. Игровая площадка Rust Я пытался с помощью tokio::sync::Mutex
и Arc
исправить эту ошибку компиляции. Но не удалось. Не могли бы вы, пожалуйста, помочь мне? @drewtato
@zhaokaixie, тебе нужно снять блокировку, прежде чем ждать будущего. Вы могли бы избежать привязки блокировки к переменной и затем ждать только будущего play.rust-lang.org/… но это немного многословно, поэтому, возможно, стоит сделать их клонируемыми play.rust-lang. орг/…
Если проблема в многословии повторения, вы наверняка можете написать функцию, которая сделает это за вас, не так ли?