import time # Монотонные часы и короткое ожидание позволяют фоновому Python-коду регулярно освобождать GIL для обработчиков Qt. class WorkBudget: # Длинный расчёт добровольно уступает процессор, чтобы поток интерфейса не ждал GIL после каждого вызова Qt. def __init__(self, cancelled=None): # Необязательное threading.Event отменяет устаревшее задание без зависимости общего алгоритма от Qt. self.cancelled = cancelled # Событие принадлежит одному заданию; его установку проверяем до и после уступки интерфейсу. self.deadline = time.perf_counter() + .002 # Две миллисекунды непрерывной работы ограничивают конкуренцию с короткими Python-обработчиками GUI. def __call__(self): # Рабочие проходы вызывают контрольную точку примерно раз в тысячу элементов, без проверки часов на каждом отсчёте. if self.cancelled is not None and self.cancelled.is_set(): # Запрос отмены должен действовать даже до истечения бюджета вычислений. raise InterruptedError("Фоновое задание отменено") # Вызывающий worker освобождает ресурсы и не публикует частичный результат. if time.perf_counter() < self.deadline: # Несколько быстрых контрольных точек не должны создавать лишние ожидания. return # Продолжаем текущую небольшую порцию вычислений до исчерпания её бюджета. if self.cancelled is None: # Неотменяемая подготовка также обязана оставлять время главному потоку. time.sleep(.001) # Пауза в одну миллисекунду освобождает GIL; sleep(0) не гарантирует времени для цепочки обработчиков Qt. elif self.cancelled.wait(.001): # Ожидание освобождает GIL и немедленно заканчивается при отмене вместо полной паузы. raise InterruptedError("Фоновое задание отменено") # Не продолжаем вычисление после отмены, полученной во время уступки интерфейсу. self.deadline = time.perf_counter() + .002 # Новый бюджет начинается после ожидания, чтобы не накапливать пропущенные интервалы. def work_items(items, checkpoint): # Оборачиваем полный последовательный проход без копирования и без потери исходных элементов. if checkpoint is None: # Обычные синхронные вызовы сохраняют прежнее поведение и не получают искусственных задержек. yield from items # Передаём значения в неизменном порядке без счётчика контрольных точек. return # Ветвь без фонового бюджета не выполняет проверки часов. for index, item in enumerate(items): # Счётчик определяет небольшие порции независимо от типа исходного контейнера. if index % 1024 == 0: # Не более 1024 элементов между проверками ограничивают задержку отмены и удержание GIL. checkpoint() # Уступаем время интерфейсу либо прерываем устаревшее задание. yield item # Отдаём каждый исходный элемент ровно один раз, сохраняя точность и порядок измерений. checkpoint() # Последняя проверка не допускает публикации результата, отменённого в конце короткого блока.