Gary H. wrote:
I have found that the recommended solution of placing cpu-intensive code
inside a thead only works well for some applications.
In particular, in my most recent application, placing the disk I/O
intensive code within a thread made a one second operation take 75
seconds. And that was with a timer setting of only 2 ms.
Hi Gary,
Regardless, this all seems backwards to me. My understanding of how this
is working is that the thread is getting one pass each time the timer
times out. What I really want is for the “intensive code” to get all of
the available time, except that I want to “interrupt” the intensive code
every 100ms to update the GUI and check for GUI clicks, etc.
I agree. I also don’t have a good mental model of what is happening, as
there is an interaction between the Ruby threads and the wx library. I
wish I could say I understood this. The following is the result only of
some experimentation, so please treat with caution!
My belief is that things should happen as you describe. When using
wxRuby, the code is using the GUI thread, which is why we must put the
heavy computation in its own thread and use a Timer. The Timer is there
so that the GUI thread wakes up every specified interval, updates the
displays/responds to events, and then passes control back to the Thread.
We have to make our own Timer (unlike with, for example, Java/Swing)
because of the separation between Ruby and the wx library, but that’s
where it gets hazy for me. I don’t think my previous example properly
captured that separation, because a GUI update is being done in the
computation Thread, and this perhaps makes the approach slow in your
case.
I experimented with my code from before, replacing the ‘sleep’ method
with an intensive computation which returns partial results as it goes
along. The best approach I could find, both in computing time and
conceptually, was to move all the GUI updates into the Timer, and to
reduce the calls to ‘append_text’ by aggregating the results. About
this last point, adding 100_000 lines to the text control separately
made the program 50 times slower than concatenating the same 100_000
lines and adding them to the text control in one go.
My test code now looks like the following:
class MyFrame < Wx::Frame
def initialize
super(nil, :title => “Heavy Computation Example”)
@text_field = Wx::TextCtrl.new(self, :style => Wx::TE_MULTILINE)
button = Wx::Button.new(self, :label => "Show Values")
evt_button(button) {print_value}
main_sizer = Wx::BoxSizer.new Wx::VERTICAL
main_sizer.add @text_field
main_sizer.add button
set_sizer main_sizer
@results = [] # for collecting the results
together
# all the GUI updating is done here
Wx::Timer.every(100) do # wake up every
100ms
@text_field.append_text “#{@results.join(”\n")}" # add text en
bloc
@results = [] # and clear
results
Thread.pass # return to
calculation
end
end
def print_value
# all the computation is done here
Thread.new do
sum = 0
1.upto(N-1) do |i|
sum += 1 if lots_of_work i # this is the ‘busy computation’
@results << sum # collect the result
end
end
end
end
I quite like the final code. There’s a conceptual separation of GUI
update and computation: it’s a traditional model-view separation. The
GUI is updated only every 100ms, reporting every intermediate result,
and the computation time(*) is indistinguishable from that of doing the
calculation from a non-GUI Ruby program (although I’ve not tried
anything taking longer than 10 seconds).
Getting back to your application, my suggestion is to move the
‘write_text’ call out of the ‘intensive_routine’ and into the timer
block. Then, reduce the number of calls to ‘write_text’ by collecting
the update lines together, and join them to add in a single call to
‘write_text’.
Let me know if this is a better/worse solution for you.
cheers,
Peter.
(*) Note, I did find execution times for the wxRuby program when the
Thread was run a second or third time were dramatically quicker than the
first run. I’m not sure why, or what to do about it.