Native Windows GUI: Multithreading
Win32 is a single threaded API. That means a window and all it's children control will be contained on a single thread (the GUI thread).
This is a problem is an application has a method that blocks the GUI thread (ex: reading a large file) because doing so will make the UI
unusable for the duration of the method. This section will explain how NWG fix this problem.
The notice object
Assuming there is a task that will block the UI thread. It's obvious the first step would be to just move this task into another thread.
But then, how does the main thread will know when the task is completed? The answer is the
Notice
object.
(Using notice objects require the
notice
feature.)
The
Notice
object is an invisible component that can send a message to their window from another thread. Here's a very simple example on how to use it:
pub struct HeavyCompute {
window: nwg::Window,
notice: nwg::Notice,
compute: RefCell<Option<thread::JoinHandle<u64>>>,
}
// ... NativeUi implementation ...
/// `Event::OnWindowInit` callback
fn on_init(app: &HeavyCompute) {
let sender = app.notice.sender();
*self.compute.borrow_mut() = Some(thread::spawn(move || {
thread::sleep(Duration::new(5, 0));
sender.notice();
1+1
}));
}
/// `Event::OnNotice` callback
fn on_notice(app: &HeavyCompute) {
let mut data = app.compute.borrow_mut();
match data.take() {
Some(data) => {
println!("THE ANSWER IS {:?}!", data);
},
None => {}
}
}
In this example, a heavy compute operation is started off the main thread after window initialization.
on_init
generates a NoticeSender
from the application notice
- The
NoticeSender
is sent to another thread
- The thread handle is saved in the application to read from it later
- Once the off-thread computation are done,
sender.notice()
is called to wake up the notice on the main GUI thread
- Finally, the
OnNotice
event is raised and the compute thread is joined to get the compute value
Running windows on multiple threads
NWG does not have any global state. As such it's possible to create as many GUI thread as you might want. Each GUI thread can have its own windows.
Blocking one of the GUI thread won't block the second. This is useful when implementing dialogs.
Example