3

Let me just preface this by saying, I will almost undoubtedly use a Queue for my program. I'm posting this question to more or less just satisfy my curiosity after spending a decent amount of time researching this topic without finding any conclusive answers.

So, the question: Is it safe to access/edit an IntVar(), DoubleVar() etc, from anywhere other than the main loop? Also, what about simply reading the value (via x.get() ) from a separate thread? I know one should not edit/update widgets from separate threads, but I have found no information regarding Intvars and the like. Any insight would be greatly appreciated.

Here's a related (but fairly old) question that was never really answered:

Python/Tkinter: Are Tkinter StringVar (IntVar, etc) thread safe?

Community
  • 1
  • 1
Elle
  • 601
  • 5
  • 18
  • 2
    Just for anyone who may come across this, I would highly recommend against using `IntVar`s, `DoubleVar`s, etc. in threads. I did some testing in Python 3.3 and the behavior is incredibly inconsistent. I modified a filling program I recently finished to test, and using either an `IntVar` or `DoubleVar` in a thread would cause the it to crash. What is most troubling, is the fact it did so unpredictably. For instance, I initially tested 100 filling cycles without any problems, then ran it again, only for it to crash on cycle 23. I ran dozens of tests like this, most of which resulted in crashes. – Elle Aug 24 '14 at 21:27
  • 1
    This unpredictability is exactly what not being thread-safe means. – Bryan Oakley Sep 05 '14 at 21:26

1 Answers1

8

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.

dano
  • 91,354
  • 19
  • 222
  • 219