Background Tasks
A background task is a thread that can comunicate with the main AppCUI threat and send/receive information from it, just like in the following diagram:

To start a background task, use the following command:
pub struct BackgroundTask<T,T>
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:
T
represents the type that will be send from the background thread to the main thread (the thread where AppCUI runs). It is usually an enum that reflects a status (e.g. precentage of the task) or a query (information needed by the background thread)R
represent the response for a query (meaning that if the background thread ask the main thread using type T, the reply will be of type R)
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 main thread to the background thread and waits until the main thread responds with an object of type R |
should_stop() | Returns true if the background thread should stop (meaning that the main thread requested the background thread to stop) or false otherwise. This method also handle Pause and Resume requests (meaning that if the main thread has requested a pause this method will wait until the main thread will request a resume or will request a stop of the background thread) |
Events
The BackgroundTaskEvents
event handler must be implementd on a window to receive events from a background task. It has 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
An usual flow for a background task is the following:
- The main thread starts a background task using the
BackgroundTask::run(...)
method - The window that receives this background task events should overwrite the
BackgroundTaskEvents<T,R>
event handler - 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 will be called via
BackgroundTaskEvents
event handler with the information sent by the background thread
Example
-
Create the coresponding type T and R that will be send between the main thread and the background thread:
enum TaskStatus { StartWithTotaltems(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::StartWithTotaltems(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) == Response::Stop { break; } } // do the actual work } }
-
Start the background task:
let task = BackgroundTask::<TaskStatus,Response>::run(background_task, window.handle());
-
Implement the
BackgroundTaskEvents
event 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<Status, 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',d: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(())
}