I have a wxRuby application that is working fine, except that it does
not update the multi-line textctrl box until the application has
completed processing.
The program has a couple of nexted loops. On the inner loop, the program
processes a file, then displays out the results, as outlined below:
...
Start of inner loop
process one file...very heavy cpu load
puts "File number #{@new_file_count} processed"
@textbox.append_text("File number #{@new_file_count} processed\n")
End of inner loop
...
The puts line is displayed in the terminal window on each pass through
the loop.
The "write to gui" line, @textbox..., does not display until all of
the
files have been processed.
Ruby should process the gui line before going any further, so that
implies that the gui does not process the line until later.
What is going on here, and what do I do to get the lines displayed in
the gui as each loop completes?
Thanks,
Gary
--
Posted via http://www.ruby-forum.com/.
Peter Lane
2009-Nov-25 19:58 UTC
[wxruby-users] wxRuby GUI textctrl not updating until end
Hi Gary, Gary Hasson wrote:> I have a wxRuby application that is working fine, except that it does > not update the multi-line textctrl box until the application has > completed processing. >...> > What is going on here, and what do I do to get the lines displayed in > the gui as each loop completes? >Have you used a thread for the busy loop? If not, that is likely your problem. I have been using the technique in the sample below to keep my GUIs updated. A timer in the Frame gets the GUI updated at regular intervals, and a Thread in the ''busy loop'' is needed to separate the work from the GUI. If you leave the Thread out from ''print_value'', then the GUI is updated only at the end. If you leave out the timer, then the Thread does not seem to work reliably, e.g. if you pass focus to another window. Let me know if this helps in your application. I would be interested to hear of a better solution though. cheers, Peter. require ''wx'' class MyFrame < Wx::Frame def initialize super(nil, :title => "Controls") @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 # -- this timer interrupts every 100ms and gets next thread active # -- if you don''t have the timer, the GUI does not update if window # in background timer = Wx::Timer.new(self, Wx::ID_ANY) evt_timer(timer.id) {Thread.pass} timer.start(100) end def print_value Thread.new do 10.times do |i| puts "#{i} cycle" @text_field.append_text "#{i} cycle\n" sleep 1.0 end end end end Wx::App.run do MyFrame.new.show end -- Posted via http://www.ruby-forum.com/.
Gary Hasson
2009-Nov-26 14:18 UTC
[wxruby-users] wxRuby GUI textctrl not updating until end
Hi Peter, Your approached worked great for my application! The sleep line did not seem to have any effect, so I deleted it. Thanks for your help! Gary -- Posted via http://www.ruby-forum.com/.
Gary Hasson
2009-Dec-02 22:37 UTC
[wxruby-users] wxRuby GUI textctrl not updating until end
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.
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 "interupt" the intensive
code
every 100ms to update the GUI and check for GUI clicks, etc.
In other words, the entire application, except for the intensive code,
would need to be inside the thread that the timer is giving attention
to. I''m not sure how to do that, or if that is the best approach.
Present code outline:
class Myframe < Wx::Frame
def initialize
normal stuff
# Timer interrupt to service thread
timer = Wx::Timer.new(self, Wx::ID_ANY)
timer.start(2)
evt_timer(timer.id) {Thread.pass}
end
def various other def''s
...
end
def intensive_routine
Thread.new do
until done do
thousands of disk accesses...
@msgbox.write_text( "Display status for user..." )
...
end
end
end
def on_init
evt_button( @quit_btn ) { on_quit_btn_clicked }
...
end
end
The above code is a greatly simplified representation of working code.
When the code is run with the "Thread.new" commented out, the program
completes quickly, but it does not update the GUI or pay attention to
GUI mouse clicks until the intensive routine has completed. When run
with the "Thread.new" uncommented, the GUI is responsive as desired,
but
the intensive routine is running at a snail''s pace.
I would appreciate guidance on this.
Thanks,
Gary
--
Posted via http://www.ruby-forum.com/.
Peter Lane
2009-Dec-03 03:17 UTC
[wxruby-users] wxRuby GUI textctrl not updating until end
Gary Hasson 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. -- Posted via http://www.ruby-forum.com/.
Gary Hasson
2009-Dec-04 21:15 UTC
[wxruby-users] wxRuby GUI textctrl not updating until end
Threads can be used to keep the GUI responsive during an I/O or CPU
intensive operation, but I have now found what seems like a better way.
The following command causes the GUI to be updated, with any pending GUI
events being handled:
Wx::THE_APP.yield()
This is what I was looking for all along! No need for threads or timers.
The "intensive routine" receives only a minor impact on its
performance
using this statement.
Simplified example code outline:
class Myframe < Wx::Frame
def initialize
normal stuff
end
def various other def''s
...
end
# Very CPU intensive routine:
def on_get_busy_btn_clicked
update_rate = 1000 # Every 1000 iterations
byte_number = 0 # Gets incremented on each iteration
until done do
thousands of disk accesses, and after each read: byte_number += 1
@msgbox.write_text( "Display status for user..." ) # Not on each
iteration
# Check GUI to see if it needs processing:
Wx::THE_APP.yield() if byte_number%update_rate == 0
...
end
end
def on_init
evt_button( @get_busy_btn ) { on_get_busy_btn_clicked }
...
end
end
The above code is a greatly simplified representation of working code.
The "intensive routine" gets nearly full CPU attention for quick
processing. The occasional call to service the GUI keeps the GUI
"alive"
and responisve.
The answer was in the WxRuby doc''s under "miscellaneous,
App", which
brings up app.html.
Regards,
Gary
--
Posted via http://www.ruby-forum.com/.