I have a GUI app, which starts one worker thread. This worker thread
does work that updates the GUI but takes a long time, and I don't want
to block the GUI. If does what it needs to do, then sends the results
back to the main thread by using thread::send to set variables in the
main thread, e.g. something like
thread::send $::tMainThreadID {set a 1}
At the same time, I'm pushing buttons in the UI etc.
I'm wondering now when the code in my thread::send will be executed in
the main thread I'm wondering because It seems that this code is
executed 'at the same time' as the code that runs when I push a
button. This means that I have to secure access to variables with
mutexes etc, and that seems strange, since these variables only live
in the main thread, given tcl's strong separation of threads.
So, basically, if I do thread::send from my worker thread to my main
thread that runs the GUI, when does this code run? Does it run 'while'
code in the main thread is running. That seems very strange, but
perhaps if the code in the main thread sleeps, vwaits, or vwaits
internally? For example I have a sleep function that does:
after $inMilliseconds [list set $theSleepVar 0]
vwait $theSleepVar
Thinking about this and about locking things with mutexes to prevent
my problems, I see that thread::mutex requires you to do a create
first, but you can't specify a name. So If I want a mutex that locks
something so another thread can't access it, I have to create the
mutex in one thread, then pass the return value of the create to the
other thread so I can use it there. This is a rather difficult way to
work with mutexes, it would seem, so perhaps I don't understand the
intended use here also?
This answer is going to seem long and complicated, but only because
you're not used to the event-oriented way of doing things.
Tcl has the concept of the "event loop", which is used to
dispatch events that occur in the outside world into programmed
actions. When you do a [bind] on a GUI, an [after] for a timer,
or a [fileevent] on a channel, you are making a request that
the event loop should call you back at the appropriate time.
The "appropriate time" in most single-threaded and well-written
Tcl/Tk scripts is simply defined as: "when the given event
occurs (the user performs the action, the time elapses, or
the channel gets into the appropriate condition), and
nothing else is tying up the event loop".
In the presence of threads, this principle doesn't change;
it's just that there's an event loop in every thread.
(Threads other than the main GUI launch their event loops
with thread::wait.) So thread::send doesn't run immediately;
it simply drops an event for the other thread's event loop
to execute when it's convenient.
So far, this is a nice clean model. Now here's the awkward part.
There are a few commands: 'update', 'vwait', 'tkwait', thread::wait'
that invoke the event loop recursively. When a script executes
one of these, the event loop runs to dispatch events (with
an earlier copy of the event loop beneath it on the stack)
until a given condition is found (the GUI is painted, a given
variable has been set, a window is destroyed, whatever).
As you have discovered, these commands are dangerous in a
large application with many events about: they allow for
recursion in code that isn't expecting to be recursive.
Any attempt to solve this problem with mutexes is only going to
make it worse, since the code that is being interfered with
is in the same thread, earlier on the stack. A solution that
is much better Tcl style is to avoid recursion through the
event loop altogether: never call [update], [vwait],
[thread::wait] or [tkwait] in an event handler. Instead,
use an appropriate binding to get a callback when the thing
you want to wait for happens, and then return to the event
loop.
http://wiki.tcl.tk/1255 has more on this.
Now with that said, onward to your second question. You
asked about how to transfer the handle returned from
[thread::mutex create] over to another thread: after all,
mutexes aren't terribly useful if they can't be seen
in multiple threads! The answer is that mutex handles are
process global, so you simply store the handle in
whatever thread is going to need it:
set mutex [thread::mutex create]
thread::send -async theOtherThread [list set mutex $mutex]
--
73 de ke9tv/2, Kevin
I was aware of the event loop, but not the recursiveness of it, so
your answer amde a few things fall into place in my head nicely,
thanks!
It explains why I am now trying protect with a mutex code in the same
thread from executing halfway through other code in that thread. And
that this is probably not the way forward...
> So far, this is a nice clean model. Now here's the awkward part.
> There are a few commands: 'update', 'vwait', 'tkwait', thread::wait'
> that invoke the event loop recursively.
Are these the only ones? (I've seen 'after xx' listed in the wiki
somewhere, which I do use.)
Also, is it common for external code (say, packages delivered with
ActiveTcl) to do run any of these commands internally, or are they
really generally avoided?
> event loop altogether: never call [update], [vwait],
> [thread::wait] or [tkwait] in an event handler. Instead,
> use an appropriate binding to get a callback when the thing
> you want to wait for happens, and then return to the event
> loop.
This seems to make sense, and all my problems may stem from attempts
to 'wait' a while in different ways. First, I found on the wiki this:
proc ::Utils::Sleep { {inMilliseconds 200} } {
set theSleepVar "[namespace current]::___sleep[clock seconds][expr
int(rand() * 1000)]___"
after $inMilliseconds [list set $theSleepVar 0]
vwait $theSleepVar
unset $theSleepVar
}
Which uses vwait so is probably the root of all my problems.
I'm also using
dostuff
after 1000
domorestuff
is that 'dangerous'? (And thinking about it, if it isn't, it's going
to block my GUI for a second. So I need to figure out an event driven
way to do the things that currently need this. Damn, changing how my
program is build up:-( )
> http://wiki.tcl.tk/1255has more on this.
> Now with that said, onward to your second question. You
> asked about how to transfer the handle returned from
> [thread::mutex create] over to another thread: after all,
> mutexes aren't terribly useful if they can't be seen
> in multiple threads! The answer is that mutex handles are
> process global, so you simply store the handle in
> whatever thread is going to need it:
>
> set mutex [thread::mutex create]
> thread::send -async theOtherThread [list set mutex $mutex]
That's what I feared (and it seems like a lot of work for no good
reason: I would just have a unique name to pass to mutex create, and
then I could use the same name where I need the mutex, without needing
to pass it around).
Thanks again!
Arnt
I now see in the docs that this is documented as 'While the command is
sleeping the application does not respond to events', so I guess it is
safe.
Arnt
> I'm also using
> dostuff
> after 1000
> domorestuff
>
> is that 'dangerous'?
[after $n] doesn't process any events, so it's not dangerous
as far as reentrancy goes. However, since [after $n] doesn't
process any events it _is_ dangerous in the sense that events
don't get processed :-)
> (And thinking about it, if it isn't, it's going
> to block my GUI for a second. So I need to figure out an event driven
> way to do the things that currently need this. Damn, changing how my
> program is build up:-( )
One way to do this in an event-driven way is to change
dostuff... ; after $n ; domorestuff...
to:
dostuff ; after $n { domorestuff... }
In the second version "domorestuff..." is evaluated at global scope,
so you'll generally need to transform the code a bit;
for example:
proc stuff {} {
set a [...]
set b [...]
after 1000
dosomethingwith $a
dosomethingwith $b
}
would become:
proc start-stuff {} {
set a [...]
set b [...]
after 1000 [list finish-stuff $a $b]
}
proc finish-stuff {a b} {
dosomethingwith $a
dosomethingwith $b
}
--Joe English
They appear in some parts of tcllib (some of the network packages use
them for their non-async code, e.g. comm and (sadly, because i have not
yet found the time to fix it) ldap.
Some gui packages use tkwait for dialogs, but just grep for those
commands in the package dir to see...
Michael
> Some gui packages use tkwait for dialogs, but just grep for those
> commands in the package dir to see...
Well, it seems that exactly this may explain my other problem...
( http://groups.google.com/group/comp.lang.tcl/browse_thread/thread/97c4892c68312e6b?hl=en
)