|
28 | 28 | import time |
29 | 29 | from concurrent.futures import Future |
30 | 30 | from queue import Queue |
| 31 | +from collections import deque |
31 | 32 |
|
32 | 33 | logger = logging.getLogger(__name__) |
33 | 34 |
|
@@ -294,6 +295,58 @@ def __log_debug(self, *args, **kwargs): |
294 | 295 | self._logger.debug(*args, **kwargs) |
295 | 296 |
|
296 | 297 |
|
| 298 | +@with_logger |
| 299 | +class _CallSoonQueue(QtCore.QObject): |
| 300 | + def __init__(self): |
| 301 | + super().__init__() |
| 302 | + # Contains asyncio.Handle objects |
| 303 | + # Use a deque instead of Queue, as we don't require |
| 304 | + # synchronization between threads here. |
| 305 | + self.__callbacks = deque() |
| 306 | + # Set a 0-delay timer on itself, this will ensure that |
| 307 | + # timerEvent gets fired each time after window events are processed |
| 308 | + # See https://doc.qt.io/qt-6/qtimer.html#interval-prop |
| 309 | + self.__timerId = self.startTimer(0) |
| 310 | + self.__stopped = False |
| 311 | + self.__debug_enabled = False |
| 312 | + |
| 313 | + def add_callback(self, handle): |
| 314 | + # handle must be an asyncio.Handle |
| 315 | + self.__callbacks.append(handle) |
| 316 | + self.__log_debug("Registering call_soon handle %s", id(handle)) |
| 317 | + return handle |
| 318 | + |
| 319 | + def timerEvent(self, event): |
| 320 | + timerId = event.timerId() |
| 321 | + assert timerId == self.__timerId |
| 322 | + |
| 323 | + # Stop timer if stopped |
| 324 | + if self.__stopped: |
| 325 | + self.killTimer(timerId) |
| 326 | + self.__log_debug("call_soon queue stopped, clearing handles") |
| 327 | + # TODO: Do we need to del the handles or somehow invalidate them? |
| 328 | + self.__callbacks.clear() |
| 329 | + return |
| 330 | + |
| 331 | + # Iterate over pending callbacks |
| 332 | + # TODO: Runtime deadline, don't process the entire queue if it takes too long? |
| 333 | + while len(self.__callbacks) > 0: |
| 334 | + handle = self.__callbacks.popleft() |
| 335 | + self.__log_debug("Calling call_soon handle %s", id(handle)) |
| 336 | + handle._run() |
| 337 | + |
| 338 | + def stop(self): |
| 339 | + self.__log_debug("Stopping call_soon queue") |
| 340 | + self.__stopped = True |
| 341 | + |
| 342 | + def set_debug(self, enabled): |
| 343 | + self.__debug_enabled = enabled |
| 344 | + |
| 345 | + def __log_debug(self, *args, **kwargs): |
| 346 | + if self.__debug_enabled: |
| 347 | + self._logger.debug(*args, **kwargs) |
| 348 | + |
| 349 | + |
297 | 350 | def _fileno(fd): |
298 | 351 | if isinstance(fd, int): |
299 | 352 | return fd |
@@ -339,6 +392,7 @@ def __init__(self, app=None, set_running_loop=False, already_running=False): |
339 | 392 | self._read_notifiers = {} |
340 | 393 | self._write_notifiers = {} |
341 | 394 | self._timer = _SimpleTimer() |
| 395 | + self._call_soon_queue = _CallSoonQueue() |
342 | 396 |
|
343 | 397 | self.__call_soon_signaller = signaller = _make_signaller(QtCore, object, tuple) |
344 | 398 | self.__call_soon_signal = signaller.signal |
@@ -441,6 +495,7 @@ def close(self): |
441 | 495 | super().close() |
442 | 496 |
|
443 | 497 | self._timer.stop() |
| 498 | + self._call_soon_queue.stop() |
444 | 499 | self.__app = None |
445 | 500 |
|
446 | 501 | for notifier in itertools.chain( |
@@ -474,6 +529,11 @@ def call_later(self, delay, callback, *args, context=None): |
474 | 529 | return self._add_callback(asyncio.Handle(callback, args, self), delay) |
475 | 530 |
|
476 | 531 | def _add_callback(self, handle, delay=0): |
| 532 | + if delay == 0: |
| 533 | + # To ensure that we can guarantee the execution order of |
| 534 | + # 0-delay callbacks, add them to a special queue, rather than |
| 535 | + # assume that Qt will fire the timerEvents in order |
| 536 | + return self._call_soon_queue.add_callback(handle) |
477 | 537 | return self._timer.add_callback(handle, delay) |
478 | 538 |
|
479 | 539 | def call_soon(self, callback, *args, context=None): |
@@ -717,6 +777,7 @@ def set_debug(self, enabled): |
717 | 777 | super().set_debug(enabled) |
718 | 778 | self.__debug_enabled = enabled |
719 | 779 | self._timer.set_debug(enabled) |
| 780 | + self._call_soon_queue.set_debug(enabled) |
720 | 781 |
|
721 | 782 | def __enter__(self): |
722 | 783 | return self |
|
0 commit comments