@@ -58,6 +58,10 @@ def __init__(
5858 self .max_scheduled = max_scheduled
5959
6060 self ._last_tick = time .ticks_ms () # NOQA
61+ # Fairness: how long the last handler pass took and when it ended,
62+ # so the timer can leave the main thread a share of the CPU.
63+ self ._last_run_ms = 0
64+ self ._last_end = self ._last_tick
6165 self ._timer .init (
6266 mode = Timer .PERIODIC ,
6367 period = self .duration ,
@@ -102,6 +106,7 @@ def is_running(cls):
102106 return cls ._current_instance is not None
103107
104108 def _task_handler (self , _ ):
109+ run_start = time .ticks_ms () # NOQA
105110 try :
106111 self ._scheduled -= 1
107112
@@ -159,6 +164,11 @@ def _task_handler(self, _):
159164 if self .exception_hook :
160165 self .exception_hook (e )
161166
167+ finally :
168+ end = time .ticks_ms () # NOQA
169+ self ._last_run_ms = time .ticks_diff (end , run_start ) # NOQA
170+ self ._last_end = end
171+
162172 def _timer_cb (self , _ ):
163173 # The only place LVGL time advances. Feed it the real elapsed time
164174 # rather than the nominal period: timer callbacks are delivered via the
@@ -170,6 +180,15 @@ def _timer_cb(self, _):
170180 if self ._running :
171181 return
172182
183+ # Fairness: a handler pass that outlasts the timer period would
184+ # otherwise be rescheduled back-to-back, starving everything else on
185+ # the main thread (the REPL, touch and app tasks). Wait until at
186+ # least a quarter of the last pass's duration has elapsed since it
187+ # ended, so the main thread always keeps ~20% of the CPU.
188+ gap = self ._last_run_ms >> 2
189+ if gap > self .duration and time .ticks_diff (now , self ._last_end ) < gap : # NOQA
190+ return
191+
173192 if self ._scheduled < self .max_scheduled :
174193 try :
175194 micropython .schedule (self ._task_handler_ref , 0 )
0 commit comments