diff --git a/book/listings/main_event_loop/10/main.rs b/book/listings/main_event_loop/10/main.rs new file mode 100644 index 000000000000..af223d58375e --- /dev/null +++ b/book/listings/main_event_loop/10/main.rs @@ -0,0 +1,94 @@ +use glib::clone; +use gtk::{Application, ApplicationWindow, Button, glib}; +use gtk::{gio, prelude::*}; + +use std::{ + sync::{Arc, Mutex}, + thread, + time::Duration, +}; + +const APP_ID: &str = "org.gtk_rs.MainEventLoop10"; + +fn main() -> glib::ExitCode { + // Create a new application + let app = Application::builder().application_id(APP_ID).build(); + + // Connect to "activate" signal of `app` + app.connect_activate(build_ui); + + // Run the application + app.run() +} + +struct DemoStruct { + text: String, +} + +impl DemoStruct { + fn mutate(&mut self, string: String) { + self.text = string.to_string(); + } +} + +fn build_ui(app: &Application) { + // Create a button + let button = Button::builder() + .label("START") + .margin_top(12) + .margin_bottom(12) + .margin_start(12) + .margin_end(12) + .build(); + + let (sender, receiver) = async_channel::bounded(1); + // ANCHOR: callback + // + // Wrap the new structure in Arc/Mutext + let demo_struct = Arc::new(Mutex::new(DemoStruct { + text: "Start".to_string(), + })); + + // Connect to "clicked" signal of `button` + button.connect_clicked(move |moved_button| { + let sender = sender.clone(); + // Get mutable reference to the structure + let arc_struct = Arc::clone(&demo_struct); + + moved_button.set_label("Working"); + + gio::spawn_blocking(move || { + let three_seconds = Duration::from_secs(3); + thread::sleep(three_seconds); + // Mutate the string on another thread + arc_struct + .lock() + .unwrap() + .mutate("Mutated on another thread".to_string()); + // Send the structe back + sender.send_blocking(arc_struct).unwrap(); + }); + }); + + glib::spawn_future_local(clone!( + #[weak] + button, + async move { + while let Ok(mutated_struct) = receiver.recv().await { + // Set the label using the string from the received structure + button.set_label(&mutated_struct.lock().unwrap().text); + } + } + )); + // ANCHOR_END: callback + // + // Create a window + let window = ApplicationWindow::builder() + .application(app) + .title("My GTK App") + .child(&button) + .build(); + + // Present window + window.present(); +} diff --git a/book/src/main_event_loop.md b/book/src/main_event_loop.md index 62d2607980ca..6883fc5148a6 100644 --- a/book/src/main_event_loop.md +++ b/book/src/main_event_loop.md @@ -237,6 +237,18 @@ If you decide to share it, you user name will be printed on the console.
Dialog requesting user information.
+## Custom structs and multithreading + +If you have a custom structure that needs to perform its function on a separate thread to avoid blocking, Rust's [Arc](https://doc.rust-lang.org/std/sync/struct.Arc.html) and [Mutex](https://doc.rust-lang.org/std/sync/struct.Mutex.html) are required for safe data transport. `Mutex` (mutual exclusion) allows data access from another thread while guaranteeing exclusivity of the request call. To request data from another thread, `lock()` is called on the `Mutext` to signal the attempt. But Rust's ownership system does not allow `Mutext` data to be safely exchanged between threads. To perform data transfer between threads, you need to utilize Rust's `Arc` structure. These `Arc`s (Atomic Reference Counting) are safe to share between the application threads. But keep in mind that this safety comes with the price and will affect the performance. In some cases it is cheaper to run operations on a single thread without spending resources on locks and reference counting. + +Lets mutate the `text` string of the `DemoStruct` structure in another thread: + +Filename: listings/main_event_loop/10/main.rs + +```rust +{{#rustdoc_include ../listings/main_event_loop/10/main.rs:callback}} +``` + ## Tokio [`tokio`](https://docs.rs/tokio/latest/tokio/) is Rust's most popular asynchronous platform.