-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes to the repaint signal causes problems with multi-threaded code #1379
Comments
I modified the Examplediff --git a/eframe/examples/download_image.rs b/eframe/examples/download_image.rs
index 9cbb51d1..c6471014 100644
--- a/eframe/examples/download_image.rs
+++ b/eframe/examples/download_image.rs
@@ -36,17 +36,39 @@ impl eframe::App for MyApp {
promise
});
- egui::CentralPanel::default().show(ctx, |ui| match promise.ready() {
- None => {
- ui.add(egui::Spinner::new()); // still loading
- }
- Some(Err(err)) => {
- ui.colored_label(egui::Color32::RED, err); // something went wrong
+ let mut new_promise = None;
+
+ egui::CentralPanel::default().show(ctx, |ui| {
+ if ui.button("Reload image").clicked() {
+ let ctx = ctx.clone();
+ let (sender, promise) = Promise::new();
+
+ let request = ehttp::Request::get("https://picsum.photos/seed/1.759706314/1024");
+ ehttp::fetch(request, move |response| {
+ let image = response.and_then(parse_response);
+ sender.send(image); // send the results back to the UI thread.
+ ctx.request_repaint(); // wake up UI thread
+ });
+
+ new_promise = Some(promise);
}
- Some(Ok(image)) => {
- image.show_max_size(ui, ui.available_size());
+
+ match promise.ready() {
+ None => {
+ ui.add(egui::Spinner::new()); // still loading
+ }
+ Some(Err(err)) => {
+ ui.colored_label(egui::Color32::RED, err); // something went wrong
+ }
+ Some(Ok(image)) => {
+ image.show_max_size(ui, ui.available_size());
+ }
}
});
+
+ if new_promise.is_some() {
+ self.promise = new_promise;
+ }
}
} The panic happens pretty consistently if the "Reload image" button is clicked many times. |
Good catch, and thanks for testing bleeding edge egui! As you point out, one solution would be to just remove the Another alternative is to put |
This alternative seems the best in my opinion since these functions are the only ones that require the multi-thread safety, allowing the single thread only code to have better performance. It could also make sense to create a separate struct that is used to request the repaints which is what users would send to their background threads, preventing them from accidentally using any other part of the context's api from the other thread. EDIT: the |
A workaround for this right now is adding this to your epaint = { version = "0.17", features = ["multi_threaded"] } |
Always use parking_lot for mutexes, i.e. always be multi-threaded. Closes #1379
https://github.com/asny/three-d recently merged a PR adding `glow` support: asny/three-d#210 This means it is a prime candidate for embedding 3D painting inside an eframe app. There are currently a few kinks that need to be figured out: ### Black screen When reusing the same three_d context over time (as one should), we only get one frame of egui together with three_d, and then after that a black screen with just the three_d painting on top. I need to fix that before merging this PR. ### `Shape: Send + Sync` `Shape` is `Send + Sync` and `three_d::Context` is not. This means we cannot store a three_d context and send it to the `Shape::Callback`. So we either need to recreate the three_d context each frame (obviously a bad idea), or access it through a `thread_local` hack. This PR adds both as examples, with a checkbox to switch. We could consider making `Shape: !Send + !Sync`, but that would mean `egui::Context` could not be `Send+Sync` either (because the egui context stores shapes). This could actually be fine. `egui::Context` should only be used from a background thread for calling request_repaint and allocating textures. These could be made separate parts of the egui Context, so that one would do: ``` rust let repaint_signal = egui_ctx.repaint_signal(); let tex_mngr = egui_ctx.tex_mngr(); std::thread::spawn(move || { // We can use repaint_signal and tex_mngr here, // but NOT `egui_ctx`. }): Related: * #1379 * #1380 * #1389
https://github.com/asny/three-d recently merged a PR adding `glow` support: asny/three-d#210 This means it is a prime candidate for embedding 3D painting inside an eframe app. There are currently a few kinks that need to be figured out: When reusing the same three_d context over time (as one should), we only get one frame of egui together with three_d, and then after that a black screen with just the three_d painting on top. I need to fix that before merging this PR. `Shape` is `Send + Sync` and `three_d::Context` is not. This means we cannot store a three_d context and send it to the `Shape::Callback`. So we either need to recreate the three_d context each frame (obviously a bad idea), or access it through a `thread_local` hack. This PR adds both as examples, with a checkbox to switch. We could consider making `Shape: !Send + !Sync`, but that would mean `egui::Context` could not be `Send+Sync` either (because the egui context stores shapes). This could actually be fine. `egui::Context` should only be used from a background thread for calling request_repaint and allocating textures. These could be made separate parts of the egui Context, so that one would do: ``` rust let repaint_signal = egui_ctx.repaint_signal(); let tex_mngr = egui_ctx.tex_mngr(); std::thread::spawn(move || { // We can use repaint_signal and tex_mngr here, // but NOT `egui_ctx`. }): Related: * #1379 * #1380 * #1389
Issue created from comment: c768d1d#r68942300
@quietvoid
Removing
frame.request_repaint
causes problems with callingctx.request_repaint
on another thread that did not exist when calling onframe
.Frame
always uses astd::sync::Mutex
to lock its state which makes this code fine to call from another thread as it will just block if the mutex is locked.Context
on the other hand switches between using anAtomicRefCell
and aparking_lot::Mutex
depending on ifepaint/multi_threaded
is enabled (which it normally is not when using eframe. So whenctx.request_repaint
is called from another thread and the ui thread has a lock on theContext
the application just panics with the following message:The solution to this would be to just have a way to enable or enable by default the
multi_threaded
feature on epaint. But it would leave this discrepency between different parts of code usingstd::sync::Mutex
,parking_lot::Mutex
andAtomicRefCell
for depending on features or just what part of the code they are in. Standardizing all of them to useparking_lot
's synchronization would be my suggestion, but again theCargo.toml
forepaint
does note a minor performance impact when not usingAtomicRefCell
in a single threaded context:egui/epaint/Cargo.toml
Lines 52 to 54 in c8f6cae
The text was updated successfully, but these errors were encountered: