1
votes

I have code that looks like this:

package require Thread

proc p1 {} {

    set tid [thread::create {
        proc executeCommand {command} { 
            return $command
        }
        thread::wait
    }]

    set result ""
    ::thread::send -async $tid [list executeCommand {"Hello thread world"}] result

    #***Do some additional stuff***

    vwait result
    ::thread::release $tid
    puts $result

    return $result

}

p1

After sourcing the .tcl file that holds this code, my expectation is for the child thread to return "Hello thread world" after vwait is called and the 'result' variable to be printed out, but neither of these happen. It appears that the 'result' variable is remaining blank.

Whats strange is that when I take the code out of a procedure (proc) block and source the .tcl file, it works perfectly but with the way my system is set up I need to use procedures.

Not sure what I am doing wrong.

1

1 Answers

1
votes

The “problem” is that the receiving variable (just as with vwait) is located with respect to the global namespace, not the variables that are in the current scope; the flag TCL_GLOBAL_ONLY is used on the call to Tcl_SetVar2Ex in the callback (and Tcl's underlying variable implementation is quite complicated so one really wants to stick to the API if possible):

/*
 * Set the result variable
 */

if (Tcl_SetVar2Ex(interp, var, NULL, valObj,
                  TCL_GLOBAL_ONLY | TCL_LEAVE_ERR_MSG) == NULL) {
    rc = TCL_ERROR;
    goto cleanup;
}

That makes sense in general, as you could have returned from the procedure between launching the background thread and receiving the result, and Tcl really tries to avoid doing early binding.

So where did the result go? It's in the global result variable (the :: just means “I really mean to use the global variable named this”):

% puts $::result
"Hello thread world"

The easiest fix for this is to use a variable to do the receiving that is unique to the particular call. Which sounds more complicated than it really is, since we've already got a unique token in the thread ID:

proc p1 {} {
    set tid [thread::create {
        proc executeCommand {command} { 
            return $command
        }
        thread::wait
    }]

    ### Make the name of the global variable (an array element) ###
    set var ::p1results($tid)

    ### This is a simple transformation of what you were already doing ###
    set $var ""
    ::thread::send -async $tid [list executeCommand {"Hello thread world"}] $var

    #***Do some additional stuff***

    vwait $var

    ### Transfer the global variable into a local and remove the global ###
    set result [set $var]
    unset $var

    ### Back to your code now ###
    ::thread::release $tid
    puts $result

    return $result
}

This appears to work as expected when I try it out.