-
-
Notifications
You must be signed in to change notification settings - Fork 685
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
Add window refresh lock to block window from updating while handler is running. #2955
base: main
Are you sure you want to change the base?
Conversation
This seems highly relevant to #2938. |
Thanks for the suggestion/patch. I definitely agree that the "refresh flood" is an issue that we need to address. I've done a quick initial review, my immediate impressions are:
Of these, (2) and (3) are things I could probably live with as "future enhancements"; but (1) and especially (4) need to be resolved before we could move forward with this approach. |
Thanks for looking into it!
I tried 'lifting' to lock to the App level, but I failed miserably. It's probably because: I would love to have some help here...
When the
I wonder if repaints are triggered when the objects are not being re-layouted, but I haven't researched that yet. Like you said: That could be saved for later.
Yes. In my original PR #2438 you indicated that having to specify that a handler wants to have refresh locked until it's done adding widgets (by explicitly using a So, could a decorator(?) or a context manager(?) be used to indicate that a handler does not want to have a refresh lock present during its implementation? This burdens the programmer... something like:
Thoughts? It almost looks as if 'let's do it automatically, without a burden on the programmer, but not always' is hard to get right :-) Christ van Willegen |
I think this isn't an issue any more if the locking API is a public window API (see point 4).
That's the call I was checking - AFAICT, the "refresh parent container" is performed on every widget refresh; while any given child widget will only be refreshed once, the parent will be refreshed once for every child.
It's backend dependent. IIRC, Winforms is the only platform that currently does "live" updates - this is usually reported as a bug when a user has a Windows app with a blocking, synchronous loop:
On Windows, this will update every second; on every other platform, it only draws the final "Loop 9" version, because control is never yielded to the event loop, so the paint operation isn't performed. Agreed we can wait on this until later; I mostly want to make sure that it's clear where such an interface would exist (i.e., that we aren't painting ourself into an architectural corner).
True - but an explicit lock that works is better than an implicit lock that doesn't :-) Plus, we can treat this as a v1 implementation - if, at some point in the future, someone works out how to make the locking completely implicit, we can make the explicit lock a no-op, and mark it as deprecated. Of the two options you present, I'd be inclined to use a context manager - a decorator would ultimately be subject to the same problems as the implicit handler approach, because there's no way to "unlock" around an |
For reference: Showing a list of (over 400) Podcast episodes in my Podcast Player (no, you don't want to see the source ;-) I told you I suck a programming Python) without a window refresh lock takes about 45 seconds. With it, the runtime goes to 5 seconds. |
(I guess the test failure is unrelated...?) |
#2938 already suggests a way to make this implicit – instead of thinking in terms of a lock, just defer any O(n) work until the next main loop iteration. The simplest solution I can think of is:
|
Yes - that one is a transient error we see occasionally; I've restarted the test. |
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.
This looks like a good implementation of the manual lock approach; a couple of fairly minor cosmetic things, but it otherwise pretty good.
The only other hesitation I have at this point is the point @mhsmith made - that this is actually not that far from a true "automatic" solution.
If instead of a locked
boolean, make it an attribute that is either None
or an async Task. when a refresh
call is made, it checks for the existence of a Task, and if it doesn't exist, creates it. All refresh calls then mark the object as dirty rather than processing; and the task becomes a co-routine that does the current refresh
implementation. That should (AFAICT) ensure that any changes are batched and implemented on the main event loop.
return self._locked | ||
|
||
@locked.setter | ||
def locked(self, locked: bool) -> None: |
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.
This flag definitely shouldn't be writable. A user shouldn't be able to inadvertently change the locking state. A getter makes sense; but I can't see a use case for releasing the lock being a public API (outside of the context manager)
@@ -8,6 +8,7 @@ | |||
assert_action_not_performed, | |||
assert_action_performed, | |||
assert_action_performed_with, | |||
performed_actions, |
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'd prefer to see this in the form of an assert_action_count
(or better still, an assert_ action_performed(count=N)
).
@@ -61,7 +61,7 @@ def test_noop_handler_with_cleanup_error(capsys): | |||
|
|||
|
|||
def test_function_handler(): | |||
"""A function can be used as a handler.""" | |||
"""A function can be used as a handler""" |
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.
Why all these updates to remove punctuation?
When a handler is running it may add a lot of widgets to a window. The window keeps updating its layout, leading to an O(n^2) in calls to the
refresh()
function. When the number of widgets added to a window is doubled, run time is quadrupled (etc).This leads to slow updates when going from view to view.
My solution is to keep a list of 'dirty' children when a window is locked, so that the children will receive a
refresh()
call when the window is unlocked. Also, a window is automatically locked when a handler is called that has awindow
in its interface. No interaction from the user or application programmer is needed to use this functionality.PR Checklist: