From: Stephen Kellett <snail / objmedia.demon.co.uk> Subject: Ruby and Tk question Date: Thu, 29 Sep 2005 19:41:45 +0900 Message-ID: <ZQx403EhK8ODFwF+@objmedia.demon.co.uk> > I've been trying to write a trivial multi-threaded application that has > a GUI (implemented using TkRuby) and which updates various counters on > the GUI. Problem is it deadlocks as soon as I try to update the > graphical part, but I am unsure why. I've included the most stripped > down version of the code here - which will not deadlock - it will just > output to stdout. If you comment in the two obvious lines (commented to > indicate which ones) then it does deadlock. > > Am I doing something wrong? > Have I made incorrect assumptions? > How should I do this? > Is it possible to update Tk components from threads other than the main > GUI thread? On Ruby/Tk, all operations and callbacks are evaluated on the eventloop thread. All calls are serialized to avoid conflict of GUI operation. Therefore, never wait or sleep on the callback operation!! Your callback "testThread1" waits two threads. That is not so serious problem except broken respense of GUI. However, each of the threads which "testThread1" waits calls a Tk operation "@counterLabel.configure". The Tk operation sends a request to the eventloop and waits for the return value from the eventloop. It makes deadlock!. If "testThread1" doen't the created threads, there is no problem except your typo ;-) > #@counterLabel.configure(text=>"Counter A: %d" % @countA) ^^^^ > #@counterLabel.configure(text=>"Counter B: %d" % @countB) ^^^^ :text or "text" However, I think, your request is "disable the menu entry between callback operations are working". If that is true, Please read the rewrited example at the end of this mail. # BTW, TkTimer class may help you sometimes. For example, # ------------------------------------------------------------- # TkTimer.new(0, 1000){ <operations of each call> }.start # ------------------------------------------------------------- # This calls the block 1000 times with 0ms (or minimum) interval. # TkTimer works on the eventloop. So, there is no thread-switching. ---------< example script >-------------------------------------- require 'tk' # get widget classes require 'thread' class RTVExample < TkRoot def initialize(args) super(args) @countA = 0 @countB = 0 @lock1 = Mutex.new # create our UI @menubar = TkMenubar.new(nil, nil) @menubar.pack('side'=>'top', 'fill'=>'x') @menubar.add_menu([['File', 0], ['Exit', proc{exitFunc}, 0]]) @th_test_mbtn, @th_test_menu = @menubar.add_menu([['Thread Test', 0], ['Test 1', proc{testThread1}, 0]]) @counterLabel = TkLabel.new{text 'Counter' ; pack {padx=30 ; pady=0}} end # why does this deadlock? - surely it should just run the two threads alongside the GUI thread. def testThread1() # create two the threads - comment in the #@counter lines to see the deadlock Thread.new do begin @th_test_menu.entryconfigure('Test 1', :state=>:disabled) tok1 = Thread.new do 10000.times { @lock1.synchronize { @countA += 1 } puts "A:" + @countA.to_s() # this next line causes the deadlock - why? @counterLabel.configure(:text=>"Counter A: %d" % @countA) Tk.update_idletasks } end tok2 = Thread.new do 10000.times { @lock1.synchronize { @countB += 1 } puts "B:" + @countB.to_s() # this next line causes the deadlock - why? @counterLabel.configure(:text=>"Counter B: %d" % @countB) Tk.update_idletasks } end begin tok1.join rescue # ignore error on Thread end begin tok2.join rescue # ignore error on Thread end ensure @th_test_menu.entryconfigure('Test 1', :state=>:normal) end end end def exitFunc() TkRoot.destroy() end end # run the GUI app gui = RTVExample.new() Tk.mainloop() ----------------------------------------------------------------- -- Hidetoshi NAGAI (nagai / ai.kyutech.ac.jp) > > Cheers > > Stephen > > require 'tk' # get widget classes > require 'thread' > > class RTVExample < TkRoot > def initialize(args) > super(args) > > @countA = 0 > @countB = 0 > @lock1 = Mutex.new > > # create our UI > > @menubar = TkMenubar.new(nil, nil) > @menubar.pack('side'=>'top', 'fill'=>'x') > > @menubar.add_menu([['File', 0], > ['Exit', proc{exitFunc}, 0]]) > > @menubar.add_menu([['Thread Test', 0], > ['Test 1', proc{testThread1}, 0]]) > > @counterLabel = TkLabel.new{text 'Counter' ; pack {padx=30 ; pady=0}} > end > > # why does this deadlock? - surely it should just run the two threads alongside the GUI thread. > > def testThread1() > # create two the threads - comment in the #@counter lines to see the deadlock > > tok1 = Thread.new do > 10000.times { > @lock1.synchronize { @countA += 1 } > puts "A:" + @countA.to_s() > > # this next line causes the deadlock - why? > #@counterLabel.configure(text=>"Counter A: %d" % @countA) > } > end > tok2 = Thread.new do > 10000.times { > @lock1.synchronize { @countB += 1 } > puts "B:" + @countB.to_s() > > # this next line causes the deadlock - why? > #@counterLabel.configure(text=>"Counter B: %d" % @countB) > } > end > > tok1.join > tok2.join > end > > def exitFunc() > TkRoot.destroy() > end > end > > # run the GUI app > > gui = RTVExample.new() > Tk.mainloop() > > -- > Stephen Kellett > Object Media Limited http://www.objmedia.demon.co.uk/software.html > Computer Consultancy, Software Development > Windows C++, Java, Assembler, Performance Analysis, Troubleshooting >