I’m writing a Gtk.jl application, and it’s easy to see that any time some time-consuming activity happens, such as running a long computation, this interferes with the UI responsivity. In especial, even updating a label with a message such as “Please wait while I perform your long computation” may not work, what can be quite annoying.
I imagine this must be due to something like the computations running in the main thread and blocking Gtk(.jl) from doing whatever it needs. What is a good way to move these computations away to a different thread? Or would tasks help? And maybe a more philosophical question: how to structure this program with computations running in parallel so that you have the same imperative step-by-step execution from the original blocking application?
Example app. You probably never get to see the first message in the label, only the final one with the computation result.
using Gtk
using LinearAlgebra
b = Gtk.Button("oi")
ll = Gtk.Label("output")
hb = Gtk.Box(:v)
w = Gtk.Window("x")
push!(hb,b)
push!(hb,ll)
push!(w, hb)
function calcpi(niter)
acc = 0
for n in 1:niter
if norm(rand(2)) < 1.0
acc += 1
end
end
return 4*acc/niter
end
signal_connect(b, "clicked") do widget
@info "Button clicked 1"
GAccessor.text(ll,"Running your long calculation...")
# niter = 100000000
niter = 10000000
# niter = 1000000
mypi = calcpi(niter)
@info mypi
GAccessor.text(ll,"done, mypy=$mypi")
end
showall(w)
Some extra info: I have tried to put the calculation in a separate thread using @spawn, and even wrote a little polling loop that prints to the console while waiting for the calculation to stop. Still the UI freezes, not only the label does not get updated, the whole UI really freezes. Should I really actually finish the callback before the UI can be updated again?
Hey @xor0110,
this has been a long term issue, but with Julia 1.3 and its threading capabilities it actually works. Have a look at this example.
The point is to spawn the thread and put all UI code into an idle_add.
One missing piece in that code is to spawn the thread at a different thread than the master thread. There is some package exactly for that purpose, but I have not tried it yet (you need to search in the forum, I do not remember the name).
Thanks, it’s good to hear from more people writing this kind of application. I think there’s a huge potential to be explored in Julia writing guis with libraries such a Gtk, which seems to be the most usable right now.
I got my application to do what I wanted, although I’m not entirely sure how it relates to you answer. What I noticed is you just have to make sure all your UI updates happen at the end of a callback call (or worker thread), and then as soon as that function is over the UI will be able to update. My scenario is different since I’m not really interested in interactive updates together with the calculations, which is of course a more interesting case.
Here’s my current example app. It requires @spawn, although I’m not even sure having actual separate threads is really required. A task taking over would work in this case since my problem is just that updates from before the calculation don’t even happen. It doesn’t really matter for me if it all gets blocked.
using Gtk
using LinearAlgebra
import Base.Threads.@spawn
b = Gtk.Button("oi")
ll = Gtk.Label("output")
hb = Gtk.Box(:v)
w = Gtk.Window("x")
push!(hb,b)
push!(hb,ll)
push!(w, hb)
state = :startcomp
signal_connect(b, "clicked") do widget
global state
if state == :startcomp
@info "Button clicked 1"
GAccessor.text(ll,"Running your long calculation...")
state = :computing
@spawn dolongcomp()
end
end
function dolongcomp()
global state
niter = 100000000
mypi = calcpi(niter)
@info mypi
GAccessor.text(ll,"done, mypy=$mypi")
state = :startcomp
end
function calcpi(niter)
acc = 0
for n in 1:niter
if norm(rand(2)) < 1.0
acc += 1
end
end
return 4*acc/niter
end
showall(w)
I had some great experience some time ago writing a Scala + Akka GUI where one actor runs in the main thread, and is the only one allowed to perform UI actions. Computations are performed by other actors which run in a separate thread pool, and communicate with the GUI actor. I would love to figure out how to do something like that in Julia. Tasks and Channels might provide enough capability for it, although I’m not sure how to even start. My example app right now is not so great because we have some logic tied straight to a button callback, and the other UI update should be triggered by some kind of custom signal. I don’t know if Gtk allows you to implement this.
I have implemented something similar. It uses a global struct where the computation thread updates the state and a UI thread/task updates the UI. I have been using a Timer for the UI task but you can also use @spawn with a thread and a sleep. But thinking longer about it, it would even better to use g_timeout_add :
This is a function that regularly calls a callback until the callback returns false.
For the global variable you should use const Ref to get no performance penalty.
That is clear. My suggestion was if you want to split the computation thread from the UI thread. g_idle_add should only get UI update to do. No computation in that callback.
OK, I think I get it then: to make sure something that needs to be run in the main thread goes there, we can use g_idle_add from whatever threads, is that it? One last question I would have, though is what actually really requires this. I’m not sure this is really the case from the GAccessor.text in my example, for instance.
yes, its not safe to call drawing code from arbitrary code and idle_add is the way to make an asynchron drawing operation. timeout_add is similar but allows repeated redrawing, which is useful, if you have a computation going on and want to monitor the computation with you UI.
I actually do not know, what operations are allowed and which not. You can google a little bit around what information you find on that. My understanding is that one should not change UI from a non-UI thread. And actually it makes, from my perspective, the code also cleaner if one uses an idle_add to trigger an asynchron UI update.