-
Notifications
You must be signed in to change notification settings - Fork 0
Document the threading model from Tkinter's POV #10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
One more comment. For nonthreaded Tcl, the thread tying limitation officially does not apply -- because it doesn't know anything about threads. And Tkinter doesn't try to hide this difference at all, and does have visibly different semantics as a result. So I had to document both cases. I did present threaded Tcl as the "first" case still. |
Sorry to hear you're still struggling with this. I agree this is an improvement on what you had before, in especially in terms of tone, etc. Unfortunately, It still seems to factually miss the mark. When I get a chance, I'll go through and add detailed comments explaining what I think the errors are. I'm not quite sure the path from that to an improved explanation of this all, but hopefully some things will come to mind. On the plus side, I do understand why you're making all the points you're making, and as someone who has spent too much time in the past fighting with Tcl's event loop (i.e. am not a staunch defender), I'll see if I can "translate" your points to make them more accurate. More soon... |
Note that I deliberately described Tcl's working in a very sketchy
manner -- or it would "sidetrack" the narration and "add confusion", as
I explained earlier.
"More accuracy" is not really needed here, it only needs to describe the
"net effect" as seen by a Tkinter user, any underlying detail can be
hand-waved. (See e.g. https://stackoverflow.com/a/49991871/648265 , the
phrase about how Python 2 reads the source.)
Just so that you don't waste your time in vain...
|
Doc/library/tkinter.rst
Outdated
Tkinter strives to allow any calls to its API from any Python threads, without any limits, as expected from a Python module. Due to Tcl's architectural restrictions, however, that stem from its vastly different threading model, this is not always possible. | ||
|
||
Tcl's execution model is based on cooperative multitasking. Control is passed between multiple interpreter instances by sending events (see `event-oriented programming -- Tcl/Tk wiki <https://wiki.tcl.tk/1772>`_ for details). | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think the multiple interpreter thing is at all relevant. Main point.. one thread, event-oriented programming model. Calling Tk.mainloop causes Tcl to grab the next event, and process it via an event handler. Once that event handler finishes, the next event can be grabbed, etc. If for some reason that event handler takes a long time to run, there could be a long period of time before the next event is processed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To amplify this, in 99.99% of Tkinter apps, which would have one instance of Tkinter(), there's only ever going to be a single Tcl interpreter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes and no. Cooperative multitasking is all about multiple independent agents coexisting in the same thread (in fact, for the purpose of cooperative multitasking, threads are irrelevant) and passing control between each other in some semi-predictable manner. Passing messages (which is what "events" effectively are) is just a very convenient way to do so that was popularized by Smalltalk back then AFAIK.
So, events are not just "signals that something happened", they both pass information and control.
In batch computations on a single-processor system, this theoretically gives the same benefits as multithreading, without the overhead. That's why no thread support in Tcl was seen as a no problem or even benefitial. (If the system needs to be responsive to external events that can arrive at any moment, this is not so "good" anymore... oh, well :) )
Multiple threads in Tcl were actually "slapped" on top of this machinery. Just like in a single thread, one instance sends a "message" to another and waits for the result. While that other one is checking for incoming messages this way or another. So, an inter-thread message still effectively leads to "passing control" between the two instances.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried to explain all this in the text, and it invariably came across as irrelevant and confusing. That's why I ultimately limited the text to mentioning just the most key an unique trait of cooperating multitasking (so the reader gets the best idea and doesn't wonder how it is relevant to "cooperative multitasking" specifically) -- passing control.
The fact that the event queue is also used to react to external input is less critical and much more obvious -- and I focus on this right in the next paragraph, too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Considered "Information and control are passed...".
We need to somehow emphasize that they are passed together though, which this doesn't do.
Since "event" implies that it carries some data, I think the current formulation gives the most accurate impression in the end.
Doc/library/tkinter.rst
Outdated
|
||
A Tcl interpreter instance has only one stream of execution and, unlike many other GUI toolkits, Tcl/Tk doesn't provide a blocking event loop. Instead, Tcl code is supposed to pump the event queue by hand at strategic | ||
moments (save for events that are generated explicitly in the same OS thread -- these are handled immediately by simply passing control from sender to the handler). As such, all Tcl commands are designed to work without an event loop running -- only the event handlers will not fire until the queue is processed. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For our purposes, does "Tcl/Tk doesn't provide a blocking event loop" really mean that Tcl/Tk does not guarantee that events will be continuously processed? Why include the parts about pumping the event queue (which I assume means calling Tcl_DoOneEvent)? The "designed to work without an event loop" also seems extraneous. Why do we care?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or to put it another way, is the key consideration that the interpreter is "actively processing events"? (to use a technically inaccurate but common sense phrase)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For our purposes, does "Tcl/Tk doesn't provide a blocking event loop" really mean that Tcl/Tk does not guarantee that events will be continuously processed?
The multithreaded GUI model that Tkinter implements typically requires that only the UI thread can access GUI elements. As such, any operation with GUI whatsoever is normally expected to require an event loop running.
This is not the case with Tcl/Tk and thus Tkinter. Tcl/Tk commands are operational without an event loop to some extent. But no-one really knows what that extent is, and how to compensate. (Look e,g. at https://stackoverflow.com/questions/4083796/how-do-i-run-unittest-on-a-tkinter-app and http://code.activestate.com/recipes/578964-robust-unittesting-of-tkinter-menu-items-with-mock/ (the latter is linked to from a deleted answer in the former that you probably can't see) to see the sheer extent of the weird things that people tried and failed to get anything resembling a predictable result!)
Doc/library/tkinter.rst
Outdated
|
||
* For non-threaded Tcl, threads effectively don't exist. So, any Tkinter call is carried out in the calling thread, whatever it happens to be (see :func:`Tk.mainloop`'s entry on how it is implemented in this case). Since Tcl has a single stream of execution, all Tkinter calls are wrapped with a global lock to enforce sequential access. So, in this case, there are no restrictions on calls whatsoever, but only one call, to any interpreter, can be active at a time. | ||
|
||
The last thing to note is that Tcl event queus are not per-interpreter but rather per-thread. So, a running event loop will process events not only for its own interpreter, but also for any others that share the same thread. This is transparent for the code though because an event handler is invoked within the context of the correct interpreter (and in the correct Python lexical context if the handler has a Python payload). There's also no harm in trying to run an event loop for two interpreters that may happen to share a queue: in threaded Tcl, such a clash is flat-out impossible because they would have to both run in the same OS thread, and in non-threaded Tcl, they would take turns processing events. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I take it this is only relevant if you have more than one Tkinter() object instantiated at the same time. (Why would anyone do that? Has there even been a promise made that you could have two separate Tkinter instances in a process at the same time? )
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tkinter doesn't have any code that intentionally limits the ability to operate multiple interpreters and multiple event loops. So, it is designed to support this scenario. This is thus a part of its public interface and as such, the domain of a reference documentation.
A user may wish to run Tcl()
s (interpreters without Tk loaded) in them or something to run some Tcl program, or for some satellite UIs. As a ref doc writer, that's none of my business really. I've got enough experience to know that users can and will invent ways to use your code beyond your wildest imagination.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not against dealing with every possible contingency in the reference docs, though I am against people having to wade through it for what is in practice a very fringe case.
We both know very well that just because there's no code to limit something from happening it doesn't mean it's designed to support it.
From a Tcl/Tk perspective, running multiple instances of Tk has always been considered a bad idea ("doctor it hurts when I do that... don't do that"). It's grudgingly been accepted that it works when done in a separate thread, though still nobody thinks it's a good idea. Tkinter should be following that lead in at least recommending not to do it. Running multiple Tcl interpreters, threads or not, however, is definitely supported and encouraged.
Having said all that, it would still be rare for a Python programmer to run more than one Tcl interpreter (i.e. it would just be the one created by Tkinter). So let's deal with the two cases separately in the Tkinter docs.
First part would be for if you're running only a single Tcl/Tk interpreter, you need to worry about X, Y, Z. (Main concern would be not running things that prevent Tcl_DoOneEvent from being called.) You may have one or more Python threads.
Second part (which we can assume will be for expert users, and can therefore use as terse terminology as you'd like) would be for if you have more than one Tcl interpreter. In that case, the stuff you've said (with some corrections) would apply. I'd use pointers to Tcl/Tk docs liberally (e.g. https://www.tcl.tk/man/tcl8.6/TclLib/Notifier.htm) rather than explaining things in the Tkinter docs where possible.
Fencing off the second part like this, so most people know not to look at it, would work for me. How about you?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I never mentioned Tk anywhere in the paragraph. Any Tcl interpreter instance must process its event queue to be able to handle asynchronous events. Tk is irrelevant here, and unless you can point me to an official doc saying that Tk imposes additional limitations here over what plain Tcl machinery allows, I don't see a need to bring it up at all.
Now, the fact that Tcl event queue is shared is one of the critical things to understand Tkinter's behavior. Note that I describe the standard GUI execution model, then bring up all cases where Tkinter significantly differs from it. This is one of them.
And it's definitely related to threading model, so it belongs in this section rather than somewhere else.
Subjectively splitting it into "basic" and "advanced" is against ref doc design and is actively detrimental to its value. By its purpose, a ref doc is intended to fill in the gaps rather than teach how to use the product. So, the most critical thing is to be able to quickly find the required topic, and to be sure that you've seen everything on it. A subjective split breaks both these promises (and has higher maintenance cost, so bad for writers, too).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can make a subsection, but I don't see a use for it for a single paragraph that describes basically the same as the previous ones -- Tcl/Tk/Tkinter's quirks with regard to control flow. If you make it a subsection, you'll have to make the previous paragraphs into ones, too, or it will look arbitrary (and highlight the paragraph rather than tone it down, too).
OTOH, if you can formulate the same with fewer text without sacrificing its value, fine by me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I even wanted to warn about nested event loops here, too, but ultimately decided against it 'cuz it's a bad usage pattern rather than API restriction.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That said, "There's also no harm... etc" smells like saying too much: e.g. that the loops will "take turns" is not really an official promise. The promise is they coexist transparently for the programmer, that's all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We seem to be at a bit of an impasse here. I suggest we invite some other people to provide feedback or suggestions, whether on process or substantive content. Based on my limited knowledge I'd suggest @serhiy-storchaka, @terryjreedy, and @zware.
Quick version of main point of contention is contained a few comments up, in mine starting "I'm not against..." (particularly last four paragraphs) and @native-api's following one starting "I never mentioned...". Essentially who needs to know what level of detail and how that looks in these docs. Thanks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When making edits from Github UI, you can choose to make a separate PR suggesting them instead of committing them to this one directly.
I suggest you do that. When you'll be actually writing stuff and thinking about what it should look like, all the downsides I'm talking about should become more visible.
python#7287 (comment)
I tried to include a comprehensive explanation of Tcl's execution model several times, and ultimately failed.
It turns out, since the text is targeted at Tkinter users, I absolutely must explain things from Tkinter's POV because that's what the user sees.
Trying to explain Tcl's side of the story has proven useless: Tkinter doesn't use Tcl's threading model at all, so it only sidetracks the story and utterly confuses things while providing nothing that would help use Tkinter.
This time, I did manage to downplay condemning Tcl's thread tying effectively AFAICS. As well as the Tcl/Tk vs other GUI toolkits contrasting.