Using a runtime
Spawning
Spawning a task is analogous to spawning a thread. It creates a new top-level task with no parent for the runtime to process.
This usually returns a JoinHandle
, similar again to the threaded version. Awaiting the join handle will pause the current task until the spawned task is
complete.
/// Downloads a set of pages concurrently
async fn download_pages(pages: Vec<String>) -> Vec<(String, Vec<u8>)> {
let mut work = Vec::with_capacity(pages.len());
for page in pages {
// spawn each download job into it's own task.
// this ensures that all the download tasks are run concurrently
let handle = tokio::spawn(async {
let bytes = download_page(&page).await;
(page, bytes)
});
// store the handles
work.push(handle);
}
let mut output = Vec::with_capacity(pages.len());
for handle in work {
// join lets us get the output of the task
output.push(handle.await.unwrap())
}
output
}
/// Downloads the page contents at the URL
async fn download_page(url: &str) -> Vec<u8> {
todo!()
}
Depending on the runtime, you may also get the ability to 'abort' or 'cancel' a task. This in theory should remove the task from any queues, but it's never that simple in practice and it usually just sets a flag that this task can be skipped instead.
Given the similar API to thread::spawn, this requires that tasks have a 'static
lifetime.
Network
Much like std::net
,
general purpose runtimes expose their own network primitives.
Also, much like how std has Read
/Write
traits,
these runtimes will often have a similar AsyncRead
/Write
traits
Timers
Finally, most runtimes offer efficient alternatives to std::thread::sleep
.
This is often extended to provide interval clocks that repeatedly fire, as well as timeouts that can cancel tasks that take too long.