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/.