• GUI is non-responsive during a long database interaction

    From Alan Grunwald@nospam.nurdglaw@gmail.com to comp.lang.tcl on Thu Mar 6 17:42:36 2025
    From Newsgroup: comp.lang.tcl

    Hi. The subject says it all really.

    In more detail, I have an application that needs to access a database
    via SELECT statement that takes around 30s to complete. Obviously I
    should look to tweak the database so as to speed it up. In the meantime,
    I tried to execute the database interaction in a separate thread (?) by running it as an [after idle] script but this didn't have the desired
    effect - i.e. the GUI remains unresponsive (btw the same thing happens
    if I run it as an [after N] script.

    Is there any way to achieve what I'm trying to do, short of using the
    full threads package, which I've never used before and would like not to
    have to get to grips with right now.

    Is there any way to kick of a database interaction with TDBC and get a callback when it completes?

    By the way, my first workaround to provide user feedback that all is
    well was to set a different cursor while the SQL statement is executing.
    I found a list of valid cursors at https://www.tcl-lang.org/man/tcl9.0/TkCmd/cursors.html, but no pictures
    of the cursors, so I wrote the script below to display them. (I decided
    to go with "watch".)

    I anticipate someone coming along in a bit to tell me that such a demo
    script already exists elsewhere.

    ############################################################################### #
    # Quick script to demonstrate available cursors
    # ###############################################################################

    package require tk

    set cursors {
    X_cursor arrow based_arrow_down
    based_arrow_up boat bogosity
    bottom_left_corner bottom_right_corner bottom_side
    bottom_tee box_spiral center_ptr
    circle clock coffee_mug
    cross cross_reverse crosshair
    diamond_cross dot dotbox
    double_arrow draft_large draft_small
    draped_box exchange fleur
    gobbler gumby hand1
    hand2 heart icon
    iron_cross left_ptr left_side
    left_tee leftbutton ll_angle
    lr_angle man middlebutton
    mouse none pencil
    plus question_arrow right_ptr
    right_side right_tee rightbutton
    rtl_logo sailboat sb_down_arrow
    sb_h_double_arrow sb_left_arrow sb_right_arrow
    sb_up_arrow sb_v_double_arrow shuttle
    sizing spider spraycan
    star target tcross
    top_left_arrow top_left_corner top_right_corner
    top_side top_tee trek
    ul_angle umbrella ur_angle
    watch xterm
    }

    set nCols 4; # Number of columns in display

    set row 0
    set col 0

    set mnu [menu .mnu]
    set m [menu $mnu.f -tearoff no]

    $mnu add cascade File -label File -menu $m

    $m add command Quit -label Quit -command exit

    . configure -menu $mnu

    set frm [ttk::frame .frm]
    pack $frm -expand yes -fill both

    foreach cursor $cursors {
    set lbl [ttk::label .frm.l$cursor -text $cursor -cursor $cursor]
    grid $lbl -row $row -column $col -padx 3 -pady 3 -sticky nsew
    if {[incr col] > $nCols} {
    set col 0
    incr row
    }
    }

    vwait forever

    --- Synchronet 3.20c-Linux NewsLink 1.2
  • From Rich@rich@example.invalid to comp.lang.tcl on Thu Mar 6 19:52:23 2025
    From Newsgroup: comp.lang.tcl

    Alan Grunwald <nospam.nurdglaw@gmail.com> wrote:
    In the meantime, I tried to execute the database interaction in a
    separate thread (?) by running it as an [after idle] script but this
    didn't have the desired effect

    An after idle script is not a "separate thread", as you've now
    discovered.

    For real threads, use the 'thread' extension:

    https://www.tcl-lang.org/man/tcl/ThreadCmd/thread.htm

    --- Synchronet 3.20c-Linux NewsLink 1.2
  • From ted@loft.tnolan.com (Ted Nolan@tednolan to comp.lang.tcl on Thu Mar 6 21:15:54 2025
    From Newsgroup: comp.lang.tcl

    In article <vqcudn$346r4$1@dont-email.me>, Rich <rich@example.invalid> wrote: >Alan Grunwald <nospam.nurdglaw@gmail.com> wrote:
    In the meantime, I tried to execute the database interaction in a
    separate thread (?) by running it as an [after idle] script but this
    didn't have the desired effect

    An after idle script is not a "separate thread", as you've now
    discovered.

    For real threads, use the 'thread' extension:

    https://www.tcl-lang.org/man/tcl/ThreadCmd/thread.htm


    Alternately you could send the queries to a seperate process
    connected by a pipe and use I/O fileevents to keep your main process
    from blocking.
    --
    columbiaclosings.com
    What's not in Columbia anymore..
    --- Synchronet 3.20c-Linux NewsLink 1.2
  • From et99@et99@rocketship1.me to comp.lang.tcl on Thu Mar 6 13:38:55 2025
    From Newsgroup: comp.lang.tcl

    On 3/6/2025 9:42 AM, Alan Grunwald wrote:
    Hi. The subject says it all really.

    -snip-

    Is there any way to achieve what I'm trying to do, short of using the full threads package, which I've never used before and would like not to have to get to grips with right now.

    -snip-

    Using Tcl Threads can be rather easy - if you know which 95% of the threads manual pages to ignore. The man pages are not meant as a tutorial, and for that, I would recommend Ashok's tcl book.

    However, your problem is trivially solved with a second thread. You simply want to run your query in the second thread and have it send back the result to a global variable which you then vwait on. Using vwait allows your gui to remain responsive.

    First off, your program and it's gui already runs in a thread, it's called the main thread. Every tcl program begins in the main thread. You only need to create a second thread:

    The structure of your second thread might be thus:

    package require Thread

    set thread_script {
    package requires...
    ... other init code if any ...
    proc query args {
    ....
    return $result
    }
    thread::wait
    }

    To use this to create the second thread:

    set tid [thread::create $thread_script]

    The variable tid is the thread id you will use below to send the thread some work to do.

    Then to do a query:

    thread::send -async $tid [list query arg1 arg2 ... ] ::result
    vwait ::result

    The above sends the query to the thread "tid" which calls the proc and returns the result in the global variable ::result (this variable ::result gets set in the main thread, not the second thread).

    You could do something between these two statements, but to just keep the gui going that's not required. You might need some sync code however, so any gui callbacks don't occur before a required result from the query. But that's another topic.

    Note: Each thread runs in a separate interpreter, so each thread gets its own (private) set of global variables.

    For example, you might want to include an init proc as well, and it could just store such things as the data base handle in a global variable where the query proc could use it later. Of course you could also use namespaces, but they're not really needed inside such a simple thread as above.

    You also can ignore the tpool package, as it's not needed if you do it as in the above layout.

    If you can come up with a small example of using your database, in a self contained small example, i.e. one that can be copy/pasted into a tcl console for example, then I suspect many here could easily give you a threaded version that would require probably no more than 10-15 lines of additional (thread) code.


    --- Synchronet 3.20c-Linux NewsLink 1.2