Threading model implementation
On nearly every platform, though, long-running tasks like mail fetches and sends are
spawned off as parallel threads, so that the GUI remains active during the transfer—it
continues updating itself and responding to new user requests, while transfers occur
in the background. While that’s true of threading in most GUIs, here are two notes
regarding PyMailGUI’s specific implementation and threading model:
GUI updates: exit callback queue
As we learned earlier in this book, only the main thread that creates windows
should generally update them. See Chapter 9 for more on this; tkinter doesn’t
support parallel GUI changes. As a result, PyMailGUI takes care to not do anything
related to the user interface within threads that load, send, or delete email. Instead,
the main GUI thread continues responding to user interface events and updates,
and uses a timer-based event to watch a queue for exit callbacks to be added by
worker threads, using the thread tools we implemented earlier in Chapter 10
(Example 10-20). Upon receipt, the main GUI thread pulls the callback off the
queue and invokes it to modify the GUI in the main thread.
Such queued exit callbacks can display a fetched email message, update the mail
index list, change a progress indicator, report an error, or close an email compo-
sition window—all are scheduled by worker threads on the queue but performed
in the main GUI thread. This scheme makes the callback update actions automat-
ically thread safe: since they are run by one thread only, such GUI updates cannot
overlap in time.
To make this easy, PyMailGUI stores bound method objects on the thread queue,
which combine both the function to be called and the GUI object itself. Since
threads all run in the same process and memory space, the GUI object queued gives
access to all GUI state needed for exit updates, including displayed widget objects.
PyMailGUI also runs bound methods as thread actions to allow threads to update
state in general, too, subject to the next paragraph’s rules.
Other state updates: operation overlap locks
Although the queued GUI update callback scheme just described effectively re-
stricts GUI updates to the single main thread, it’s not enough to guarantee thread
safety in general. Because some spawned threads update shared object state used
by other threads (e.g., mail caches), PyMailGUI also uses thread locks to prevent
operations from overlapping in time if they could lead to state collisions. This
includes both operations that update shared objects in memory (e.g., loading mail
headers and content into caches), as well as operations that may update POP mes-
sage numbers of loaded email (e.g., deletions).
Where thread overlap might be an issue, the GUI tests the state of thread locks,
and pops up a message when an operation is not currently allowed. See the source
code and this program’s help text for specific cases where this rule is applied.
A PyMailGUI Demo| 1029