I had another idea. I want to create the slow widgets in background
auxiliary threads and keep them hidden (unpacked) until they're needed.
After toying with threads and this idea for some time it seems it works,
I've accomplished everything except that I can't see any way to bring
the widget from the auxiliary thread to the main thread and application.
Is there a way?
Luc <luc@sep.invalid> wrote:
I had another idea. I want to create the slow widgets in background
auxiliary threads and keep them hidden (unpacked) until they're needed.
After toying with threads and this idea for some time it seems it works,
I've accomplished everything except that I can't see any way to bring
the widget from the auxiliary thread to the main thread and application.
Is there a way?
No, not to transfer a Tk widget from one thread to another.
Tcl threads are share nothing threads (each interperter is isolated
from the others), and Tk widgets are created in a particuar interpreter
and can only exist in the interpreter they were created in.
The most you can do is have the background thread periodically do a 'thread::send' to the thread where the widget exists to do a small bit
of the work at a time.
Here's your demo code modified to use a 'threads' mentod for filling the
text widget:
#!/usr/bin/tclsh
package require Tk
package require Thread
set sb [scrollbar .sb -orient vertical -command [list .tx yview]]
set tx [text .tx -yscrollcommand [list $sb set]]
pack $tx -side left -fill both -expand 1
pack $sb -fill y -side right
focus $tx
bind . <Escape> [list exit 0]
font create myfont -family Freesans -size 14
image create photo IMAGE -file /usr/share/icons/hicolor/32x32/apps/kicad.png
set begin [clock milliseconds]
set end [clock milliseconds]
puts "[expr {$end - $begin}] ms elapsed in main interpreter"
set thread [thread::create -preserved]
thread::send $thread [list set parent [thread::id]]
thread::send -async $thread {
set begin [clock milliseconds]
for {set x 1} {$x <= 2000} {incr x} {
thread::send $parent [subst -nocommands {
set b [button .b$x -font myfont -text $x -width 80 -cursor arrow -image IMAGE -compound left]
\$tx window create end -window \$b
\$tx insert end "\n"
}]
after 100
}
set end [clock milliseconds]
puts "[expr {$end - $begin}] ms elapsed in thread interpreter"
thread::send -async $parent {thread::release $thread}
}
Note that it inserts a new row every 100ms, so that it is slow enough
to "see" the insertions happening. Reduce the wait in the 'after 100'
to speed things up. You can also remove the after wait for the
"fastest possible" fill. With no after wait at all the thread takes
7388ms here to finish filling the text.
Wouldn't it be far simpler to just use a proc with an after that keeps propagating itself:
proc do_a_widget args {
... build this widget ...
after 100 do_a_widget ... ;# next widget
}
after 0 do_a_widget ...
The only work the separate thread is doing is a for loop.
However, it seems that if the buttons are independent of the text
widget, i.e. they don't have to be in the same toplevel or frame
etc. then it would be possible to build the buttons in one thread,
and leave them there.
Then any action, i.e. button press -command would simply do a
thread::send back to main which would then handle the button push.
Likewise, any button config would have to be a thread::send to the
thread with the buttons. The difficulty could be synchronizing the
positions of the buttons vs the text widget. Think of it as having 2
windows along side each other.
However, only Luc can determine if all this extra bookkeeping is
worth the hassle. It seems that a better approach would be to only
generate the buttons on demand. Maybe have a proc that takes a start
button number and a count of buttons and will display them - and
build any new ones not already built. How long could it take to
create a screen-full of them?
I am having a lot of frustration with the scope of variables.
Rich posted something like this:
thread::send -async $thread {
set begin [clock milliseconds]
for {set x 1} {$x <= 8500} {incr x} {
thread::send $parent [subst -nocommands {
\$tx insert end "$x: "
\$tx image create end -image IMAGE
\$tx insert end ":$x\n"
}]
}
set end [clock milliseconds]
puts "[expr {$end - $begin}] ms elapsed in thread interpreter"
thread::send -async $parent {thread::release $thread}
}
Escaping the variables with \$ only works with globals.
I have the thread running inside a proc. Man, I can't find a way to
see the thread see the variables within the scope of the proc.
Things begin to work when I use globals, but that will be cluttering
the global namespace too much during real usage. Even tsv is not
working. Is there a way?
But this is me we are talking about and I messed that up too. When I
ported the idea to the real application, it works, but everything
freezes completely until the task is done. I must be doing something
wrong again of course.
On Wed, 3 Jan 2024 20:32:52 -0000 (UTC), Rich wrote:
Yes, setup a proc in the GUI thread that will be called to do the work
of "adding" things, one at a time, to the text widget.
Then repeatadly call the proc (either with an after loop, or from a
background thread). If all you are doing is repeatadly calling the
proc via the event loop, an after loop will be in general simpler.
I've read this multiple times and can't understand what you mean.
Either way, I've made tsv to work and everything is working now...
except that the application still freezes while the task is running.
I use -async in all thread::send commands and it's still not working as
it should. And I basically copied everything from the prototype that
works, just chaning the specific/relevant commands.
Sigh. I'm stumped again. :-(
Yes, setup a proc in the GUI thread that will be called to do the work
of "adding" things, one at a time, to the text widget.
Then repeatadly call the proc (either with an after loop, or from a >background thread). If all you are doing is repeatadly calling the
proc via the event loop, an after loop will be in general simpler.
On Wed, 3 Jan 2024 20:32:52 -0000 (UTC), Rich wrote:
Yes, setup a proc in the GUI thread that will be called to do the work
of "adding" things, one at a time, to the text widget.
Then repeatadly call the proc (either with an after loop, or from a >>background thread). If all you are doing is repeatadly calling the
proc via the event loop, an after loop will be in general simpler.
I've read this multiple times and can't understand what you mean.
except that the application still freezes while the task is running.
I use -async in all thread::send commands and it's still not working
as it should. And I basically copied everything from the prototype
that works, just chaning the specific/relevant commands.
Sigh. I'm stumped again. :-(
Sigh. I'm stumped again. :-(
Somewhere in the app you are blocking the event loop from running.
You'll have to hunt that down and fix it. Most likely you were already >blocking the event loop on /something/ before you copied in the thread >example, but you didn't notice you were blocking it because you
expected the app to freeze while the widget was loaded.
On Wed, 3 Jan 2024 23:40:24 -0000 (UTC), Rich wrote:
Sigh. I'm stumped again. :-(
Somewhere in the app you are blocking the event loop from running.
You'll have to hunt that down and fix it. Most likely you were already >>blocking the event loop on /something/ before you copied in the thread >>example, but you didn't notice you were blocking it because you
expected the app to freeze while the widget was loaded.
Can you give me one or two examples of how I could possibly be blocking
the event loop? I honestly don't know what to look for.
Another question: like I said, you posted something like this:
thread::send -async $thread {
set begin [clock milliseconds]
for {set x 1} {$x <= 8500} {incr x} {
thread::send $parent [subst -nocommands {
\$tx insert end "$x: "
\$tx image create end -image IMAGE
\$tx insert end ":$x\n"
}]
}
set end [clock milliseconds]
puts "[expr {$end - $begin}] ms elapsed in thread interpreter"
thread::send -async $parent {thread::release $thread}
}
It works fine in the global scope.
When I put that in a proc, the last line throws an error saying that
there is no $thread variable. How do I make that work correctly inside
a proc?
Can you give me one or two examples of how I could possibly be blocking
the event loop? I honestly don't know what to look for.
Simple, anytime any of your Tcl code is running, the event loop is
blocked.
When you say "put that in a proc" what do you mean. Do you mean
literallly wrapping "proc something {args} { ... } around the code
above verbatim?
If so, then go back and look at what I posted and note that if you
wrapped "the above snippet" in a proc, that you overlooked or ignored
the line above thread::send where the "thread" variable was defined.
It holds the thread id of the created child thread (and you need a
thread's id value to be able to "send" anything to it. You also
probably overlooked the other line where a thread send was used to set
a variable 'parent' in the child thread telling it the thread id value
of its parent.
On Thu, 4 Jan 2024 06:35:18 -0000 (UTC), Rich wrote:
Can you give me one or two examples of how I could possibly be blocking
the event loop? I honestly don't know what to look for.
Simple, anytime any of your Tcl code is running, the event loop is
blocked.
I had no clue what you meant by that but I spent a long time rebuilding
a lot of the app brick by brick and found out that the problem was I
was sending an entire foreach loop to the $parent and apparently that is
a no-no.
I am doing things in a way now that I think will work. Not terribly
sure yet, a lot more testing still needed, but I'll see.
When you say "put that in a proc" what do you mean. Do you mean
literallly wrapping "proc something {args} { ... } around the code
above verbatim?
Yes.
If so, then go back and look at what I posted and note that if you
wrapped "the above snippet" in a proc, that you overlooked or ignored
the line above thread::send where the "thread" variable was defined.
It holds the thread id of the created child thread (and you need a
thread's id value to be able to "send" anything to it. You also
probably overlooked the other line where a thread send was used to set
a variable 'parent' in the child thread telling it the thread id value
of its parent.
Again, I didn't understand what you meant.
Or at least I assumed you meant I should make the thread ID variable
global. I did that and it works. Thanks.
I have a new problem now.
I need to know when the thread is done. So I adds a variable to the end
of the thread {script} then use 'vwait variable' to know it's done.
It works, except that, well...
I have a big listing of files and directories. I am looking at them.
I open a directory. The application automatically detects that one of
its subdirectories is large and needs to be cached and stored in the "freezer." Which is done pronto. And when it's done, my "selection"
(whatever line was selected before the caching started) is still
"selected" like nothing happened. Good.
As soon as I use vwait anywhere in the caching/freezing code, that
selection is no longer kept. There is no selection anymore. So I press
an arrow key and the selection jumps to the bottom of the text widget.
Which is really weird because 1. the widget with the selection is not involved in the caching procedure and 2. there is nothing I can imagine
that would mess with the selection, and adding vwait to the code is
really all it takes for the phenomenon to happen. I observed that
behavior in another one of the prototypes I built to investigate the
threads.
Do you have any ideas why that happens? I really need to know when the
thread is finished so I can "pack" the finished tab in a neat, properly labeled Tupperware before it's nicely tucked into the freezer.
There's nothing wrong with sending a foreach loop, unless...
If you really need to do something once the background work is
complete, then create a proc that does the "something" and have the >background thread call that proc (via thread::send) when it is done to
cause that additional work to happen, after the "loading" work is
finished.
Sysop: | Keyop |
---|---|
Location: | Huddersfield, West Yorkshire, UK |
Users: | 307 |
Nodes: | 16 (2 / 14) |
Uptime: | 64:15:07 |
Calls: | 6,915 |
Files: | 12,379 |
Messages: | 5,431,611 |