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
>