async fn get_url(url: String) -> String {
get(url)
}
Do not block in asynchronous code. This will quickly result in no threads being left in the thread pool to handle asynchronous code! You need to use spawn_blocking to tell tokio that your get call will block.
You can see this by inserting a print in your code.
Code with print
fn main() {
let mut runtime: Runtime = Builder::new().threaded_scheduler().build().unwrap();
runtime.block_on(async {
let base_url = "https://jsonplaceholder.typicode.com/albums";
let mut handles = Vec::with_capacity(100);
let start = Instant::now();
for i in 1..=100 {
let url = format!("{}/{}", base_url, i);
handles.push(tokio::spawn(get_url(url, i)));
}
let responses: Vec<String> = try_join_all(handles).await.unwrap();
let duration = start.elapsed();
println!("# : {}\n{}", responses.len(), responses.last().unwrap());
println!("Time elapsed in http get is: {:?}", duration);
println!("Average time for get is: {}s", duration.as_secs_f64() / (responses.len() as f64));
});
}
async fn get_url(url: String, n: usize) -> String {
println!("Started {}", n);
get(url)
}
The tasks are not all started at the same time, but (in my case as I have six cores) six at the time, as that's the number of core threads tokio will use at most. It runs in three seconds.
Compare to using spawn_blocking
Code with spawn_blocking
fn main() {
let mut runtime: Runtime = Builder::new().threaded_scheduler().build().unwrap();
runtime.block_on(async {
let base_url = "https://jsonplaceholder.typicode.com/albums";
let mut handles = Vec::with_capacity(100);
let start = Instant::now();
for i in 1..=100 {
let url = format!("{}/{}", base_url, i);
handles.push(tokio::task::spawn_blocking(move || get_url(url, i)));
}
let responses: Vec<String> = try_join_all(handles).await.unwrap();
let duration = start.elapsed();
println!("# : {}\n{}", responses.len(), responses.last().unwrap());
println!("Time elapsed in http get is: {:?}", duration);
println!("Average time for get is: {}s", duration.as_secs_f64() / (responses.len() as f64));
});
}
fn get_url(url: String, n: usize) -> String {
println!("Started {}", n);
get(url)
}
This starts every single request simultaneously and finishes after half a second. Tokio has two kind of threads: those running asynchronous code and those running blocking code. Note that this starts 100 OS threads immediately (tokio will use up to 512 threads for blocking code by default), although it would reuse them if you didn't request them to run at the same time.