I want to hold join handles to any async function, in order to abort them when ctrl+c
is given in my cli application (Have noticed async functions continue to run after ctrl+c
)
I want a taskmanager which i can use in my exit handler to abort any running async tasks, but how do i store any joinhandle? using JoinHandle<dyn std::any::Any>
throws:
the size for values of type
(dyn Any + 'static)
cannot be known at compilation time
So how do I store any joinhandle?
use std::collections::HashMap;
use tokio::task::JoinHandle;
pub struct TaskManager {
tasks: HashMap<String, JoinHandle<_>>
}
Once your #[tokio::main] async fn main()
returns, all tasks are automatically aborted and the program terminates. Aborting all outstanding tasks by hand isn't necessary, unless you want to ensure they are aborted before you do something else; if you plan on having your program terminate after aborting all outstanding tasks, just return from main()
.
However, if you still want some way to manually abort tasks, there's a few options.
The simplest, if you only need the ability to abort tasks, is to store a closure that invokes JoinHandle::abort()
instead of storing the JoinHandle
itself. For example:
use std::collections::HashMap;
use tokio::task::JoinHandle;
pub struct TaskManager {
tasks: HashMap<String, Box<dyn FnOnce()>>,
}
impl TaskManager {
pub fn abort_all(&mut self) {
for (_, abort) in self.tasks.drain() {
abort();
}
}
pub fn add_task<T: 'static>(&mut self, key: String, handle: JoinHandle<T>) {
self.tasks.insert(key, Box::new(move || handle.abort()));
}
}
A more complicated approach that allows you to do other things than abort tasks would be to write a trait with methods mirroring those of JoinHandle
. This allows you to "erase" knowledge of the T
generic argument of JoinHandle
.
In this example, the trait is ErasedJoinHandle
. Here I show abort
defined on this trait, but you could add other methods from JoinHandle
as well.
use std::collections::HashMap;
use tokio::task::JoinHandle;
pub struct TaskManager {
tasks: HashMap<String, Box<dyn ErasedJoinHandle>>,
}
trait ErasedJoinHandle {
fn abort(&self);
}
impl<T> ErasedJoinHandle for JoinHandle<T> {
fn abort(&self) {
JoinHandle::abort(self);
}
}
impl TaskManager {
pub fn abort_all(&mut self) {
for (_, handle) in self.tasks.drain() {
handle.abort();
}
}
pub fn add_task<T: 'static>(&mut self, key: String, handle: JoinHandle<T>) {
self.tasks.insert(key, Box::new(handle));
}
}