对于同一个功能,我有两种方法,我正在试着看看哪一种更惯用或更有表现力.
query_many_points()
函数的目标是接受两个大小相同的数组,并通过query_point()
函数将它们并发地发送到API,返回一个Response
s的向量--一个包含从API反序列化的数据的定制 struct .API响应的顺序必须与输入的顺序完全匹配.我相信这两种方法都能做到这一点.
我最初是通过收集VEC中的期货,然后迭代到await
并按顺序展开结果来编写这篇文章的.
Approach 1: Vec of handles个
fn query_many_points(xs: &[f64], ys: &[f64]) -> anyhow::Result<Vec<Response>> {
let agent = ureq::agent();
let semaphore = Arc::new(Semaphore::new(TASKS_LIMIT));
// Concurrently query all given coordinates, limited to TASKS_LIMIT concurrent tasks
// Returned Vec<Response> preserves order of input coordinates
let runtime = Runtime::new()?;
runtime.block_on(async {
// Create a vector of task handles, preserving order with input coordinates
let handles = xs
.iter()
.zip(ys.iter())
.map(|(&x, &y)| {
// Obtain permit from semaphore when available
let permit = semaphore.clone().acquire_owned();
let agent = agent.clone();
// Spawn new task and query point, returning handle containing Result<Response>
tokio::spawn(async move {
let result = query_point(x, y, &agent);
drop(permit);
result
})
})
.collect::<Vec<_>>(); // this line is where the approaches start to differ
// Await all tasks to complete in order and collect into Vec<Response>
let mut responses = Vec::with_capacity(handles.len());
for handle in handles {
responses.push(handle.await??);
}
Ok(responses)
})
}
然后我找出了大约futures::stream::FuturesOrdered
个,试了一试,想出了第二个解决方案.
Approach 2: FuturesOrdered个
fn query_many_points(xs: &[f64], ys: &[f64]) -> anyhow::Result<Vec<Response>> {
let agent = ureq::agent();
let semaphore = Arc::new(Semaphore::new(TASKS_LIMIT));
// Concurrently query all given points, limited to TASKS_LIMIT concurrent tasks
// Returned Vec<Response> preserves order of input points
let runtime = Runtime::new()?;
runtime.block_on(async {
// Create a FuturesOrdered of task handles
let handles = xs
.iter()
.zip(ys.iter())
.map(|(&x, &y)| {
// Obtain permit from semaphore when available
let permit = semaphore.clone().acquire_owned();
let agent = agent.clone();
// Spawn new task and query point, returning handle containing Result<Response>
tokio::spawn(async move {
let result = query_point(x, y, &agent);
drop(permit);
result
})
})
.collect::<FuturesOrdered<_>>(); // this line is where the approaches start to differ
// Await completion of all tasks in order and collect into Vec<Response>
handles.try_collect::<Vec<_>>().await?.into_iter().collect()
})
}
哪种方法是最好的?还有没有其他可以改进的地方?两者维护的秩序完全相同,对吗?