У меня есть список функций с переменными аргументами, и я хочу случайным образом выбрать одну из них во время выполнения и вызвать ее в цикле. Я хочу повысить производительность моего решения.
У меня есть функция, которая вычисляет аргументы на основе некоторой случайности, а затем (должна) возвращать указатель на функцию, которую я мог бы затем вызвать.
pub async fn choose_random_endpoint(
&self,
rng: ThreadRng,
endpoint_type: EndpointType,
) -> impl Future<Output = Result<std::string::String, MyError>> {
match endpoint_type {
EndpointType::Type1 => {
let endpoint_arguments = self.choose_endpoint_arguments(rng);
let endpoint = endpoint1(&self.arg1, &self.arg2, &endpoint_arguments.arg3);
endpoint
}
EndpointType::Type2 => {
let endpoint_arguments = self.choose_endpoint_arguments(rng);
let endpoint = endpoint2(
&self.arg1,
&self.arg2,
&endpoint_arguments.arg3,
rng.clone(),
);
endpoint
}
EndpointType::Type3 => {
let endpoint_arguments = self.choose_endpoint_arguments(rng);
let endpoint = endpoint3(
&self.arg1,
&self.arg2,
&endpoint_arguments.arg3,
rng.clone(),
);
endpoint
}
}
}
Ошибка, которую я получаю,
expected opaque type `impl Future<Output = Result<std::string::String, MyError>>` (opaque type at <src/calls/type1.rs:14:6>)
found opaque type `impl Future<Output = Result<std::string::String, MyError>>` (opaque type at <src/type2.rs:19:6>)
. Компилятор советует мне await
конечные точки, и это решает проблему, но есть ли при этом накладные расходы на производительность?
Внешняя функция:
Предположим, что есть цикл, вызывающий эту функцию:
pub async fn make_call(arg1: &str, arg2: &str) -> Result<String> {
let mut rng = rand::thread_rng();
let random_endpoint_type = choose_random_endpoint_type(&mut rng);
let random_endpoint = choose_random_endpoint(&rng, random_endpoint_type);
// call the endpoint
Ok(response)
}
Теперь я хочу вызывать make_call
каждые X секунд, но я не хочу, чтобы мой основной поток блокировался во время вызовов конечной точки, так как это дорого. Я полагаю, что правильный подход к этому — порождать новый поток каждые X секунд интервала, этот вызов make_call
?
Кроме того, с точки зрения производительности: наличие такого количества клонов на ринге кажется довольно дорогим. Есть ли более производительный способ сделать это?
«это решает проблему, но есть ли накладные расходы на производительность» Имеет ли смысл сравнивать производительность сломанного и работающего кода?
Почему у вас есть «async» и «impl Future» в качестве возвращаемого типа?
Ошибка, которую вы получаете, не связана с асинхронностью. Это то же самое, что вы получаете, когда пытаетесь вернуть два разных итератора из функции . Ваша функция, как написано, даже не должна быть асинхронной. Я собираюсь удалить async
из него, когда он не нужен, но если вам нужен async
(например, для реализации асинхронного признака), вы можете добавить его обратно, и он, вероятно, будет работать так же.
Я сократил ваш код до более простого примера с той же проблемой (площадка):
async fn a() -> &'static str {
"a"
}
async fn b() -> &'static str {
"b"
}
fn a_or_b() -> impl Future<Output = &'static str> {
if rand::random() {
a()
} else {
b()
}
}
Если вы хотите вернуть трейт, но конкретный тип, реализующий этот трейт, неизвестен во время компиляции, вы можете вернуть трейт-объект. Фьючерсы должны быть Unpin
ожидаемыми, поэтому здесь используется закрепленная коробка (игровая площадка).
fn a_or_b() -> Pin<Box<dyn Future<Output = &'static str>>> {
if rand::random() {
Box::pin(a())
} else {
Box::pin(b())
}
}
Вам может понадобиться, чтобы тип был чем-то вроде Pin<Box<dyn Future<Output = &'static str> + Send + Sync + 'static>>
в зависимости от контекста.
Я думаю, что единственная причина, по которой вы бы сделали это, - это если вы хотите сгенерировать будущее с помощью какого-то асинхронного rng, затем сделать что-то еще, а затем запустить сгенерированное будущее после этого. В противном случае нет необходимости иметь вложенные фьючерсы; просто ждите внутреннего будущего, когда вы их называете (игровая площадка).
async fn a_or_b() -> &'static str {
if rand::random() {
a().await
} else {
b().await
}
}
Это концептуально эквивалентно методу Pin<Box>
, только без выделения Box
. Вместо этого у вас есть непрозрачный тип, который реализует сам себя.
Поведение блокировки у них немного отличается. Future
будет блокировать неасинхронные вещи, когда вы его вызываете, в то время как Pin<Box>
блокирует неасинхронные вещи, где вы его ожидаете. Вероятно, это в основном случайная генерация.
Блокирующее поведение конечной точки такое же и зависит от того, что там происходит внутри. Он будет блокировать или не блокировать, где бы вы ни ждали.
Если вы хотите, чтобы несколько вызовов async
происходили одновременно, вам все равно придется делать это вне функции. Используя среду выполнения tokio, это будет выглядеть примерно так:
use tokio::task;
use futures::future::join_all;
let tasks: Vec<_> = (0..100).map(|_| task::spawn(make_call())).collect();
let results = join_all(tasks).await;
Это также позволяет вам делать другие вещи во время работы фьючерсов между make_call
и collect();
.
Если что-то внутри вашей функции блокируется, вы хотите создать это с помощью let results
(а затем дождаться этого дескриптора), чтобы вызов await в task::spawn_blocking
не блокировался.
Если ваша среда выполнения многопоточная, make_call
будет проблемой. Вы можете создать тип, реализующий ThreadRng
с помощью from_entropy, и передать его в свои функции. Или вы можете вызвать Rng + Send
или даже просто thread_rng
куда вам нужно. Это создает новый rng для каждого потока, но будет повторно использовать их при последующих вызовах, поскольку это статический поток, локальный. С другой стороны, если вам не нужна такая случайность, вы можете с самого начала использовать тип rand::random
.
Если ваша среда выполнения не является многопоточной, вы сможете пройти Rng + Send
полностью, при условии, что средство проверки заимствования достаточно умно. Однако вы не сможете передать его в асинхронную функцию, а затем &mut ThreadRng
, поэтому вам придется создать новый внутри этой функции.
Пожалуйста, выложите полную ошибку из
cargo check
, а не из вашей IDE (у rust-analyzer сейчас есть способ ее показать), и если можете, воспроизводимый пример.