My app freezes when I use emit to update status on GUI.
I want to know the reason or how to avoid this freeze. Thanks for your review.
My test environment
The demo app is present below.
#!/usr/bin/env ruby
# encoding: UTF-8
#
require 'Qt'
class App < Qt::MainWindow
signals 'test()'
slots 'on_test()'
def initialize
super
@label = Qt::Label.new
self.centralWidget = @label
self.show
connect self, SIGNAL('test()'), SLOT('on_test()')
start_count
end
def start_count
Thread.new do
loop {
emit test()
}
end
end
def on_test()
@label.text = @label.text.to_i + 1
end
end
app = Qt::Application.new(ARGV)
App.new
app.exec
@hyde Thank you for you answer.
Solution 2 of qtbindings seems to be no help.
connect self, SIGNAL('test()'), SLOT('on_test()')
=>
connect self, SIGNAL('test()'), SLOT('on_test()'), Qt::BlockingQueuedConnection
Solution 1 was tested and the app runs fluently.
The code of Solution 1:
#!/usr/bin/env ruby
# encoding: UTF-8
#
require 'Qt'
class App < Qt::MainWindow
slots 'on_test()'
def initialize
super
@label = Qt::Label.new
self.centralWidget = @label
self.show
@c = Qt::AtomicInt.new
start_count
start_timer
end
def start_count
Thread.new do
loop {
@c.fetchAndAddRelaxed(1)
}
end
end
def start_timer
t = Qt::Timer.new(self)
t.start(16)
connect t, SIGNAL('timeout()'), SLOT('on_test()')
end
def on_test()
@label.text = @c.fetchAndAddRelaxed(0) + 1
end
end
app = Qt::Application.new(ARGV)
App.new
app.exec
Speculation on reason: the Qt main thread event loop never gets to process application events, because it is too busy delivering queued signals. The signals are queued, because they are between threads, so slot gets called in the right thread, independent of the emitting thread (and they must be queued in this case, because slot manipulates GUI objects, which is only allowed from the main thread)
There are a few alternatives to solve this. I don't really know Ruby, or its Qt bindings, so here's just a rough outline:
Create an atomic variable. Either use some Ruby type, or QAtomicInt
(I assume that's possible with the Ruby Qt bindings).
In your thread loop, just increment the atomic variable, instead of emitting a signal.
Add a QTimer
for updating the @label.text
at desired intervals. If the user is supposed to be able to read the number, I'd suggest something like 500 ms interval. Minimum sensible interval is something like 16 ms for ~60 fps update rate.
Connect the timer timeout to on_test
and get the value of the atomic integer to update the text.
That way updating of the numeric value is independent of displaying it.
Use connection type BlockingQueuedConnection (note: do not use with single thread). Add that connection type to the signal connection statement (however you do it with Ruby Qt). Then the emitting thread will block until the target slot is actually called, so the signals will only be emitted at the rate they can be processed, no faster.