Работа с длительными операциями
Задачи
Скрипты на python выполняются в основном потоке приложения или по другому в потоке интeрфейса. Если операция будет выполняться слишком долго, то интерфейс «зависнет» и будет невозможно со стороны пользователя понять это ошибка или программа все-таки продолжает выполнять свою работу. Чтобы этого избежать длительные вычисления следует выносить в фоновые потоки. В потоке интерфейса во время выполнения фоновой задачи есть возможность показывать прогресс операции.
С помощью класса AxipyAnyCallableTask
можно превратить любую пользовательскую
функцию в задачу, либо, что еще проще, воспользоваться методом axipy.TaskManager.run_and_get()
:
def user_heavy_function(arg1: int, arg2: str):
print(f"Переданные аргументы: {arg1}, {arg2} \n")
return 1
spec = ProgressSpecification(
description="Длительная операция",
flags=ProgressGuiFlags.CANCELABLE,
with_handler=False)
result = task_manager.run_and_get(spec, user_heavy_function, "Hi, ", "world!")
assert result == 1
Если объем кода в одной функции будет сильно увеличиваться или понадобится
запускать новые задачи внутри одной базовой, тогда лучше воспользоваться
наследованием от базового класса AxipyTask
и
переопределить метод run
.
Представление прогресса операции
Для обмена информацией между выполняемой задачей и элементом, отображающим прогресс,
используется класс AxipyProgressHandler
. Типичный вариант
использования выглядит следующим образом:
def user_heavy_function(ph: AxipyProgressHandler, count: int):
# Вначале задаём верхнюю планку изменения прогресса
ph.set_max_progress(count)
for i in range(0, count):
if ph.is_canceled():
break
# Тут делаем длительные вычисления
ph.add_progress(1)
return ph.progress()
spec = ProgressSpecification(
description="Длительная операция",
flags=ProgressGuiFlags.CANCELABLE)
times = 6
real_times = task_manager.run_and_get(spec, user_heavy_function, times)
# выводим колличество раз которое отработал цикл внутри
print(real_times)
ProgressSpecification
- это вспомогательная структура
данных, которая используется для первичной инициализации элемента, отображающего
прогресс. Вместо is_canceled
можно
использовать raise_if_canceled
если нужно выйти из нескольких вложенных вызовов функций или циклов.
Выполнение задач и многопоточность
Важно понимать, что задачи будут выполняться не в потоке интерфейса, поэтому внутри
этих задач нельзя отображать никакие графические элементы (QWidget
).
Так же нужно внимательно следить за тем какие ресурсы могут использоваться несколькими
потоками и при необходимости использовать различные механизмы синхронизации ( мьютексы,
локи и т.д.). Общее правило при работе с несколькими потоками следующее:
старайтесь чтобы каждая задача содержала в себе все необходимые данные для
выполнения. Синхронизацию, если она необходима, следует использовать только в
момент получения результата. Это упростит код и сведет к минимум количество ошибок.
Переданные на выполнения задачи ставятся в общую очередь, которая распределяется между физическими ядрами процессора в порядке добавления. Поэтому нет гарантии, что переданная задача выполнится мгновенно, а так же то что группа задач будет выполняться именно в порядке добавления. Если задача предполагает долгое ожидание без интенсивных нагрузок на процессор, то лучше воспользоваться стандартными python потоками. Типичные примеры таких задач это загрузка ресурсов с диска или скачивание файлов по сети.