1 unstable release
0.1.0 | Dec 20, 2023 |
---|
#788 in Concurrency
19KB
250 lines
Execute long running computational tasks, optionally transmitting a status back
Example usage
use status_executor::{StatusExecutor, StatusSender, StdContext};
// status data need to be Send + 'static and also implement Clone
#[derive(Debug,Clone)]
struct MyStatus {
progress: i32,
msg: String,
}
// lets some heavy work instead of this
let e = StatusExecutor::new(StdContext::default(), |s| {
let mut p = 0;
while p < 100 {
std::thread::sleep(std::time::Duration::from_secs_f32(0.4));
s.send(MyStatus {
progress: p,
msg: "Working...".to_string(),
});
p += 15;
}
// post that we are done!
s.send(MyStatus {
progress: p,
msg: "Done!".to_string(),
});
p
});
// your gui or whatever might grab the most recent (or none, if none is available) status
while !e.is_done() {
match e.status() {
Some(s) => println!("{} - currently at {}", s.msg, s.progress),
None => std::thread::yield_now(),
}
}
// the most recent status is also saved even if nothing new is produced.
match e.latest_status() {
Some(s) => println!("{} - last at {}", s.msg, s.progress),
None => {
assert!(false, "We produced *at least* one status, this should ont happen");
}
}
// result is an Option<Arc<T>> because otherwise you would have to either move it out of the internal state
// or track the locking used for the internal state
//let res = e.result().expect("function returned is_done() == true, this should exist now");
// you can, however just take the result as well, consuming the executor.
let res = e.take_result();
assert_eq!(res,Some(105));
Motivation
Sometimes the need arises to offload computation tasks. Async does not handle this well, and tokio etc. give you specific contexts and thread pools to run computation tasks on instead. And, as of writing this note, outside of async contexts, there isn't really anything to just ease these type of fire-off-once takes-forever tasks.
status_executor provides two Structs - Executor
and StatusExecutor
for this task.
Executor itself is nothing but a overblown join handle. Using it becomes interesting if you enable the rayon feature.
Then you can call Executor::new
using RayonContext
instead, which makes the work spwan in rayons global pool instead.
StatusExecutor
is the name giving part. Say, you have a gui application, and you are doing heavy lifting in the background.
Users might be interested in the current state (it might, after all, take 20 minutes or so).
The obvious Solution is a channel. However, it becomes a bit tedious to write this rapidly.
So StatusExecutor
is a little helper for that.
Panic behaviour
Threads running in an executor might panic
If the rayon feature is used, rayon does have a panic handler that will usually lead to an abort (unless you modify the thread pools panic handler)
Mirroring this, the StdContext
will abort the process
Using RayonGlobalContext
or RayonContext
will instead just use the set panic handler - which might be a panic as well.
Dependencies
~0–265KB