Closed enkore closed 3 years ago
Like I said above, this doesn't just happen on a normal Linux desktop, but only affects certain configurations. (Due to the somewhat absurd slowness I suspect that in my configuration draw calls might be executing synchronously over the network).
The configuration that causes this isn't actually anything fancy or broken: It's the default Gnome theme (okay, maybe that actually is a broken configuration ;D). It has these self-hiding tiny scroll bars on top of the scrollbar-bearing window's content. These are animated. So whenever you move the mouse over or out of a scrollbar-bearing window in the default Gnome theme, it will redraw itself at a rate of about 30+ redraws per second for one second. If you swipe over it, it will do that, then wait a second, then fade the scrollbars out again, which causes another second of redrawing. While the damaged regions are small (just what's under the scroll bars plus some margins), the wxListCtrl doesn't optimize for partial redraws. It always redraws everything on paint events and relies on the DC's clipper to cull drawing operations outside the damaged regions.
So each animation frame causes the wxListCtrl to do most of the work as if it were redrawing everything, which isn't all that fast. Because there is a constant onslaught on events (probably because GTK tries to redraw at 60 Hz, but my six core workstation is not fast enough to pull that off), it never runs out of events during these animations, and hence Pending()
always has events to go on and doesn't return false.
So workaround No. 1 seems to work just fine to reduce the impact of these "Gnome design decisions".
I'm not sure it's closed as it autoclosed when I merged, but it's reasonable to add this extra asyncio.sleep and shoudn't create issues.I also updated the package in pypi.
There are clearly still some locks in the event loop, but they are difficult to remove. The ones that I know of are: "Move the window", "Enter in a menu", or "Using Modal dialogs". It would be really cool though to remove all these small locks.
wx is hardcoded to just use wxGUIEventLoop directly for stuff like modal event loops, so there is probably no possibility of easily - or cleanly - injecting our logic into these.
If wx had asyncio-style event loop policies, that wouldn't be a problem. Ironic - I always found event loop policies mildly useless in asyncio... now there'd be an obvious use, but not in asyncio... :D
I don't know about GTK or macOS or other implementations, but for windows the issue is with the windows os message loop and not even wxwidgets code (e.g. wx c++ code). It is basically freezes in some situtations like on modals, on menu's and while moving a window.
Detached from https://github.com/sirk390/wxasync/issues/9#issuecomment-727922996
can take a long time for various reasons. A particularly noticeable offender is wxListCtrl in LC_VIRTUAL mode, which in certain X11 configurations (I'm not really sure what causes it, but I'm ready to blame VNC anyway) can cause this loop to take up to ~1.5 seconds(!). Dispatch() and DispatchTimeout() themselves don't usually process a single event. They generally process many events, which in practice may take 0.1-0.2 s in some cases. I've observed this loop processing several thousand events in around a hundred iterations, taking several seconds to complete.
Modal event loops (including context/popup menus) block the main event loop as well.
These "hiccups" don't block the app itself - the event loop is running after all - but coroutines and callbacks are only run when the "MainLoop" coroutine yields, which only happens between these loops.
For gathering data my main loop has now grown into this... uh... neat abomination:
This makes it really easy to figure out what the issue is:
Sure enough, the window has a wxListCtrl in LC_VIRTUAL mode with eight columns and 45 or so visible rows. The ListEvent events are EVT_LIST_CACHE_HINT events (ignored in my case). Adding a wxListCtrl to the test case presented in #9 shows the same problem:
Adding a log call to the FilterEvent method of WxAsyncApp can directly show how slowly these are emitted:
Like I said above, this doesn't just happen on a normal Linux desktop, but only affects certain configurations. (Due to the somewhat absurd slowness I suspect that in my configuration draw calls might be executing synchronously over the network).
Now the interesting question is of course what can we do about this?
Several things:
Yield inside the loop, to limit the damage being done:
This isn't particularly clean or smart, but should reduce the latency enough that it isn't immediately obvious to a human. A one or two second hang between "button click" and "results show up on screen" is very obvious, an extra 100 ms less so.
Somehow yield in FilterEvent (careful: might reduce event throughput due to overhead). I'm pretty sure this would be a somewhat ugly hack with asyncio, since it lacks any kind of run_once / tick notion. Maybe
asyncio.run_until_complete(asyncio.sleep(0))
would work. Maybe it doesn't.Ensure perfect coupling [1]. I don't think this is possible with asyncio and wxEventLoop. It's what I built asynker for a few years ago, but it's somewhat annoying, since asynker is very specifically not asyncio, so there is a lot less functionality and no compatibility with 3rd party libs written for asyncio.
[1] Perfect coupling: Event loop A yields to event loop B as soon as B has runnable tasks and vice versa. Perfect coupling is always the case when A and B are identical (that's how asynker does it; it doesn't have a separate event loop). Perfect coupling is very desirable in my opinion, because achieving it coincidentally also tends to mean that there is no additional delay between events in A causing a coroutine in B to become runnable. It also means the overall mechanism of A and B doesn't have to constantly run like MainLoop in wxasync has to do; it only needs to run when there are events to process.
(eref 42d478fb6976)