Background Tasks
A background task is a thread that can communicate with the main AppCUI thread and exchange information with it, as in the following diagram:
To start a background task, use the following command:
pub struct BackgroundTask<T, R>
where
T: Send + 'static,
R: Send + 'static,
{ ... }
impl<T: Send, R: Send> BackgroundTask<T, R> {
pub fn run(task: fn(conector: &BackgroundTaskConector<T, R>),
receiver: Handle<Window>) -> Handle<BackgroundTask<T, R>>
{...}
}
where:
Tis the type sent from the background thread to the main thread (where AppCUI runs). It is usually an enum that reflects status (for example, a percentage of work done) or a query payload the main thread should interpret.Ris the response type for a query: when the background thread asks the main thread with a value of type T, the main thread answers with a value of type R (seequerybelow).
A BackgroundTask object has the following methods:
| Method | Description |
|---|---|
read(...) | Reads the last object of type T sent by the background thread to the main thread |
send(...) | Sends an object of type R from the main thread to the background thread |
stop() | Requests the background thread to stop |
pause() | Requests the background thread to pause |
resume() | Requests the background thread to resume |
update_control_handle(...) | Updates the handle of the control that will receive events from the background thread. This is usually required if we want to close the window that shows the background thread |
A BackgroundTaskConector<T, R> is an object that can be used to send/receive information between the background thread and the main thread. It has the following methods:
| Method | Description |
|---|---|
notify(...) | Sends an object of type T from the background thread to the main thread |
query(...) | Sends an object of type T from the background thread to the main thread and blocks until the main thread responds with R. The method returns Option<R> (None if the send or receive fails). |
should_stop() | Returns true if the background thread should stop (the main thread requested a stop), or false otherwise. This method also handles Pause and Resume: if the main thread requested a pause, it blocks until the main thread requests resume or stop. |
Events
The BackgroundTaskEvents handler must be implemented on a window to receive events from a background task. It defines the following methods:
trait BackgroundTaskEvents<T: Send+'static, R: Send+'static> {
fn on_start(&mut self, task: &BackgroundTask<T,R>) -> EventProcessStatus {
// Called when the background task is started
EventProcessStatus::Ignored
}
fn on_update(&mut self, value: T, task: &BackgroundTask<T,R>) -> EventProcessStatus {
// Called when the background task sends information to the main thread
// via the BackgroundTaskConector::notify(...) method
// if the return value is EventProcessStatus::Processed, the main thread will
// repaint the window
EventProcessStatus::Ignored
}
fn on_finish(&mut self, task: &BackgroundTask<T,R>) -> EventProcessStatus {
// Called when the background task is finished
EventProcessStatus::Ignored
}
fn on_query(&mut self, value: T, task: &BackgroundTask<T,R>) -> R;
// Called when the background task sends a query to the main thread
// via the BackgroundTaskConector::query(...) method
// The main thread must return a value of type R
}
Flow
A typical flow for a background task is the following:
- The main thread starts a background task using the
BackgroundTask::run(...)method - The window that receives background task events should implement the
BackgroundTaskEvents<T, R>trait - The background thread will start and will send information to the main thread using the
BackgroundTaskConector::notify(...)method - The background thread should check from time to time if the main thread requested a stop or a pause (via the
BackgroundTaskConector::should_stop()method) - The main thread is notified through the
BackgroundTaskEventsimplementation when the background thread sends data
Example
-
Create the corresponding types T and R that will be exchanged between the main thread and the background thread:
enum TaskStatus { StartWithTotalItems(u64), Progress(u64), AskToContinue, } enum Response { Continue, Stop, } -
Create a window that will receive events from a background task:
#[Window(events = BackgroundTaskEvents<TaskStatus,Response>)] struct MyWindow { ... } -
Create a function / lambda that will execute the background task:
fn background_task(conector: &BackgroundTaskConector<TaskStatus,Response>) { // send a start message and notify about the total items // that need to be processed by the background thread let total_items = 100; conector.notify(TaskStatus::StartWithTotalItems(total_items)); // start processing items for crt in 0..total_items { // update the progress status to the main thread conector.notify(TaskStatus::Progress(crt)); // check to see if the main thread has requested a stop if conector.should_stop() { break; } // if needed ask the main thread if it should continue // this part is optional if crt % 10 == 0 { if conector.query(TaskStatus::AskToContinue) == Some(Response::Stop) { break; } } // do the actual work } } -
Start the background task:
let task = BackgroundTask::<TaskStatus,Response>::run(background_task, window.handle()); -
Implement the
BackgroundTaskEventsevent handler for the window:impl BackgroundTaskEvents<TaskStatus, Response> for MyWindow { fn on_start(&mut self, task: &BackgroundTask<TaskStatus, Response>) -> EventProcessStatus { // called when the background task is started EventProcessStatus::Processed } fn on_update(&mut self, value: TaskStatus, task: &BackgroundTask<TaskStatus, Response>) -> EventProcessStatus { match value { // process the task status value } EventProcessStatus::Processed } fn on_finish(&mut self, task: &BackgroundTask<TaskStatus, Response>) -> EventProcessStatus { // called when the background task is finished EventProcessStatus::Processed } fn on_query(&mut self, value: TaskStatus, task: &BackgroundTask<TaskStatus, Response>) -> Response { // return a response to the query Response::Continue } }
Example
The following example shows how to create a window that will receive events from a background task and update a progress bar:
use appcui::prelude::*;
enum Status {
Start(u32),
Progress(u32),
}
#[Window(events = ButtonEvents+BackgroundTaskEvents<Status,bool>)]
struct MyWin {
p: Handle<ProgressBar>,
}
impl MyWin {
fn new() -> Self {
let mut w = Self {
base: window!("'Background Task',a:c,w:50,h:8,flags:sizeable"),
p: Handle::None,
};
w.p = w.add(progressbar!("l:1,t:1,r:1,h:2"));
w.add(button!("&Start,l:1,b:0,w:10"));
w
}
}
impl ButtonEvents for MyWin {
fn on_pressed(&mut self, handle: Handle<Button>) -> EventProcessStatus {
BackgroundTask::<Status, bool>::run(my_task, self.handle());
EventProcessStatus::Processed
}
}
impl BackgroundTaskEvents<Status, bool> for MyWin {
fn on_update(&mut self, value: Status, _: &BackgroundTask<Status, bool>) -> EventProcessStatus {
let h = self.p;
if let Some(p) = self.control_mut(h) {
match value {
Status::Start(value) => p.reset(value as u64),
Status::Progress(value) => p.update_progress(value as u64),
}
EventProcessStatus::Processed
} else {
EventProcessStatus::Ignored
}
}
fn on_query(&mut self, _: Status, _: &BackgroundTask<Status, bool>) -> bool {
true
}
}
fn my_task(conector: &BackgroundTaskConector<Status, bool>) {
conector.notify(Status::Start(100));
for i in 0..100 {
if conector.should_stop() {
return;
}
std::thread::sleep(std::time::Duration::from_millis(100));
conector.notify(Status::Progress(i));
}
conector.notify(Status::Progress(100));
}
fn main() -> Result<(), appcui::system::Error> {
let mut a = App::new().build()?;
a.add_window(MyWin::new());
a.run();
Ok(())
}