Skip to content

Commit 61c23d2

Browse files
committed
rewrite threading model section to separate normal use from potential traps
1 parent 77c674b commit 61c23d2

File tree

1 file changed

+51
-56
lines changed

1 file changed

+51
-56
lines changed

Doc/library/tkinter.rst

Lines changed: 51 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,15 @@ Tcl
4141
interface to the Tk toolkit. The Tcl library has a C interface to
4242
create and manage one or more instances of a Tcl interpreter, run Tcl
4343
commands and scripts in those instances, and add custom commands
44-
implemented in either Tcl or C. Each interpreter instance also provides
45-
an event queue, used for I/O, timers, and by modules.
44+
implemented in either Tcl or C. Tcl provides for an event queue, used
45+
for I/O, timers, and by modules.
4646

4747
Tk
4848
Tk is a module that can be loaded into a Tcl interpreter instance. It adds
4949
Tcl commands (implemented in C) to create and manipulate GUI widgets. Each
5050
:class:`Tk` object embeds its own Tcl interpreter instance with Tk loaded into
5151
it. Tk's widgets are very customizable, though at the cost of a dated appearance.
52-
Tk uses the interpreter's event queue to generate and process GUI events. Note
52+
Tk uses Tcl's event queue to generate and process GUI events. Note
5353
that unlike some GUI libraries, each interpreter uses only a single thread,
5454
which has implications for :mod:`tkinter` users (see `Threading model`_).
5555

@@ -85,59 +85,54 @@ Python interpreter.
8585
Threading model
8686
---------------
8787

88-
A Tcl interpreter has one stream of execution and, unlike most other GUI
89-
toolkits, doesn't provide a blocking event loop.
90-
Instead, Tcl code is supposed to pump the event queue regularly at strategic
91-
moments -- after an action that triggers an event, before a later action that
92-
needs the result of that event. As such, all Tcl commands are designed to work
93-
without an event loop running -- only event handlers will not be executed
94-
until the queue is processed. This allows to work completely in one thread,
95-
i.e. even in environments that have no thread support at all.
96-
97-
This is in stark constrast to the execution model for most other GUI toolkits:
98-
a dedicated OS thread (usually called the "UI thread") runs the GUI event loop
99-
constantly, and other threads send work items to it -- either complete with
100-
payload reference, or as agreed-upon "events" which result in it executing a
101-
predefined "event handler".
102-
Likewise, for any lengthy tasks, the UI thread can launch worker threads that
103-
report back on their progress via the same event queue.
104-
105-
Tkinter strives to provide the best of both worlds. The "modern" multithreaded
106-
GUI model is the primary mode of execution, but pumping messages manually
107-
instead is also supported. What is more important, any Tkinter calls can be
108-
made from any Python threads, only subject to Tcl's architectural restictions:
109-
110-
* A nonthreaded Tcl doesn't know anything about threads, so all Tcl calls are
111-
wrapped with a global lock to enforce sequential access.
112-
Any Tkinter calls can be made from any threads, but under the hood, only one
113-
is active at any moment.
114-
115-
* A threaded Tcl interpreter instance, when created, becomes tied to the
116-
creating OS thread ("the interpreter thread"). Tcl mandates that all calls
117-
to the instance must come from this thread, save for special inter-thread
118-
communication APIs. Tkinter implements calls from outside the interpreter
119-
thread by posting an event to the interpreter's queue, then waits for the
120-
result of its processing. As such:
121-
122-
* To make calls from outside the interpreter queue, :func:`mainloop`
123-
must be running in the interpreter queue. Otherwise, a
124-
:class:`RuntimeError` is raised.
125-
126-
* A few select functions can only be called from the interpreter thread.
127-
128-
Tcl event queue is per-thread rather than per-interpreter. For nonthreaded Tcl,
129-
there's only one global queue shared by all interpreters; for threaded Tcl,
130-
one queue per OS thread that's shared by all interpreters bound to that thread.
131-
So, when an event processing function like :func:`Tk.mainloop` is called, it
132-
will process events not only for the calling :class:`Tk` instance
133-
but for all whose interpreters share the queue.
134-
Events handlers will be called within the same Tcl interpreter that they were
135-
bound to as per the `bind <https://www.tcl.tk/man/tcl8.6/TkCmd/bind.htm>`_ man
136-
page, but any exceptions will be raised from the event processing function.
137-
There's no harm in calling :func:`mainloop` for two :class:`Tk`s at the same
138-
time: with nonthreaded Tcl, they will take turns handling events, and with
139-
threaded Tcl, this is only allowed if the :class:`Tk`s are associated with
140-
different threads anyway.
88+
Python and Tcl/Tk have very different threading models, which :mod:`tkinter`
89+
tries to bridge. If you use threads, you may need to be aware of this.
90+
91+
A Python interpreter may have many threads associated with it. In Tcl, multiple
92+
threads can be created, but each thread has a separate Tcl interpreter instance
93+
associated with it. Threads can also create more than one interpreter instance,
94+
though each interpreter instance can be used only by the one thread that created it.
95+
96+
Each :class:`Tk` object created by :mod:`tkinter` contains a Tcl interpreter.
97+
It also keeps track of which thread created that interpreter. Calls to
98+
:mod:`tkinter` can be made from any Python thread. Internally, if a call comes
99+
from a thread other than the one that created the :class:`Tk` object, an event
100+
is posted to the interpreter's event queue, and when executed, the result is
101+
returned to the calling Python thread.
102+
103+
Tcl/Tk applications are normally event-driven, meaning that after initialization,
104+
the interpreter runs an event loop (i.e. :func:`Tk.mainloop`) and responds to events.
105+
Because it is single-threaded, event handlers must respond quickly, otherwise they
106+
will block other events from being processed. To avoid this, any long-running
107+
computations should not run in an event handler, but are either broken into smaller
108+
pieces using timers, or run in another thread. This is different from many GUI
109+
toolkits where the GUI runs in a completely separate thread from all application
110+
code including event handlers.
111+
112+
If the Tcl interpreter is not running the event loop and processing events, any
113+
:mod:`tkinter` calls made from threads other than the one running the Tcl
114+
interpreter will fail.
115+
116+
A number of special cases exist:
117+
118+
* Tcl/Tk libraries can be built so they are not thread-aware. In this case,
119+
:mod:`tkinter` calls the library from the originating Python thread, even
120+
if this is different than the thread that created the Tcl interpreter. A global
121+
lock ensures only one call occurs at a time.
122+
123+
* While :mod:`tkinter` allows you to create more than one instance of a :class:`Tk`
124+
object (with it's own interpreter), all interpreters that are part of the same
125+
thread share a common event queue, which gets ugly fast. In practice, don't create
126+
more than one instance of :class:`Tk` at a time. Otherwise, it's best to create
127+
them in separate threads and ensure you're running a thread-aware Tcl/Tk build.
128+
129+
* Blocking event handlers are not the only way to prevent the Tcl interpreter from
130+
reentering the event loop. It is even possible to run multiple nested event loops
131+
or abandon the event loop entirely. If you're doing anything tricky when it comes
132+
to events or threads, be aware of these possibilities.
133+
134+
* There are a few select :mod:`tkinter` functions that presently work only when
135+
called from the thread that created the Tcl interpreter.
141136

142137

143138
Module contents

0 commit comments

Comments
 (0)