Based on comments in the source code for the _tkinter
module, it seems like tkinter actually is at least intended to be thread-safe, as long as Tcl was built with the --enable-threads
option. This is backed up by a resolved bug on the Python tracker (issue11077) stating that tkinter isn't thread-safe, where it was eventually determined that all thread-safety issues with tkinter were bugs that were fixed in Python 2.7.3+
Here is what the _tkinter
module's source says on the issue:
The Tcl interpreter is only valid in the thread that created it, and all Tk activity must happen in this thread, also. That means that the mainloop must be invoked in the thread that created the interpreter. Invoking commands from other threads is possible; _tkinter will queue an event for the interpreter thread, which will then execute the command and pass back the result. If the main thread is not in the mainloop, and invoking commands causes an exception; if the main loop is running but not processing events, the command invocation will block.
So, as long as the mainloop is actively running in the main thread of the application, tkinter will schedule the method to run in the main thread automatically, which would make it thread-safe. That said, most sources out on the internet, aside from the actual Tkinter source code and the above bug report, indicate that using tkinter with threads is inviting a crash. I'm not quite sure what to believe, though in some small examples I tried, updating the GUI from a thread worked fine.
Now, you were specifically wondering if the thread-safety rules related to Tk widgets applied to Variable
sub-classes, too. It does: Here's some of the implementation of Variable
, the parent of IntVar
:
class Variable:
_default = ""
_tk = None
def __init__(self, master=None, value=None, name=None):
"""Construct a variable
MASTER can be given as master widget.
VALUE is an optional value (defaults to "")
NAME is an optional Tcl name (defaults to PY_VARnum).
If NAME matches an existing variable and VALUE is omitted
then the existing value is retained.
"""
# ...snip...
if not master:
master = _default_root
self._master = master
self._tk = master.tk
def set(self, value):
"""Set the variable to VALUE."""
return self._tk.globalsetvar(self._name, value)
When you set
a variable, it calls the globalsetvar
method on the master widget associated with the Variable
. The _tk.globalsetvar
method is implemented in C, and internally calls var_invoke
, which internally calls WaitForMainLoop
, which will attempt schedule the command for execution in the main thread, as described in the quote from the _tkinter
source I included above.
static PyObject*
var_invoke(EventFunc func, PyObject *selfptr, PyObject *args, int flags)
{
/* snip */
/* The current thread is not the interpreter thread. Marshal
the call to the interpreter thread, then wait for
completion. */
if (!WaitForMainloop(self))
return NULL;
/* snip */
static PyObject *
Tkapp_GlobalSetVar(PyObject *self, PyObject *args)
{
return var_invoke(SetVar, self, args, TCL_LEAVE_ERR_MSG | TCL_GLOBAL_ONLY);
}
Note that this code path is used for get operations, too, so both set
and get
operations are governed by the same rules.