21. (П10) Реалізація фонових задач без блокування інтерфейсу¶
Передумови¶
- Прочитана Лекція 20 — Багатопоточність у PySide6
- Запущений приклад із розділу «Воркер і
moveToThread: базовий патерн»
Мова в коді
Усі рядкові літерали, написи на кнопках, заголовок вікна, імена файлів та змінних — лише латиницею. Своє ім'я, прізвище та групу пишіть у транслітерації: Ivan Petrenko, KI-31. Кирилиця допускається тільки в коментарях.
Завдання¶
Написати два окремі файли з однаковим вікном і однаковою «довгою роботою» — циклом із паузами SLEEP_MS мілісекунд на кожному з STEP_COUNT кроків:
blocking_task.py— робота виконується прямо в слоті кнопки;threaded_task.py— та сама робота винесена у воркер (QObject+moveToThread+QThread).
Мета практичної — побачити різницю власними руками: перше вікно на час роботи повністю замерзає, друге лишається живим.
Персоналізація¶
Константи оголошуються на початку кожного файлу:
STUDENT_NAME = "Ivan Petrenko" # ваше ім'я та прізвище транслітерацією
STUDENT_GROUP = "KI-31" # ваша група
APP_TITLE = "Task Demo" # назва застосунку
STEP_COUNT = 10 # скільки кроків у роботі
SLEEP_MS = 500 # пауза на кожному кроці, мілісекунд
Заголовок вікна: Task Demo - Ivan Petrenko, KI-31.
Схема вікна¶
Вікно однакове в обох файлах:
+--------------------------------------------------+
| QWidget Task Demo - Ivan Petrenko, KI-31|
+--------------------------------------------------+
| Ready |
| ^- status_label |
| |
| [#############-----------------------] |
| ^- progress_bar (0 .. STEP_COUNT) |
| |
| [ Start ] |
| ^- start_button |
| |
| [ Click me (0) ] |
| ^- click_button: лічильник клацань |
+--------------------------------------------------+
Click me — це «датчик життя» вікна: він нічого не рахує, лише збільшує число у своєму написі.
Що має робити застосунок¶
| Момент | status_label |
progress_bar |
start_button |
|---|---|---|---|
| запуск | Ready |
0 |
доступна |
| під час роботи | Working... |
росте на кожному кроці | вимкнена |
| після роботи | Done |
STEP_COUNT |
знову доступна |
Файл 1: blocking_task.py¶
- пауза робиться викликом
time.sleep(SLEEP_MS / 1000)у слоті кнопкиStart; - прогрес оновлюється на кожному кроці —
progress_bar.setValue(step); processEvents()використовувати заборонено.
Файл 2: threaded_task.py¶
- клас
Worker(QObject)із сигналамиprogress = Signal(int)таfinished = Signal()і слотомrun(); - пауза робиться через
QThread.currentThread().msleep(SLEEP_MS), а неtime.sleep(); - у
Workerнемає жодного віджета й жодного імпорту зPySide6.QtWidgets; - вікно створює
QThreadіWorker, викликаєworker.moveToThread(thread), з'єднуєthread.started→worker.run, аprogressіfinished— зі своїми слотами; threadіworkerзберігаються в полях вікна (self.thread,self.worker), а не в локальних змінних;worker.finishedз'єднано зthread.quit;- у
closeEvent()вікно робитьthread.quit()іthread.wait(3000), якщо потік ще працює.
Що перевірити руками¶
- Запустити
blocking_task.py, натиснутиStartі одразу клацатиClick me— число не змінюється, вікно не перемальовується, прогрес стоїть на місці (або взагалі не малюється). - Під час роботи
blocking_task.pyспробувати перетягнути вікно — воно не рухається; система може підписати заголовокNot Responding. - Запустити
threaded_task.py, натиснутиStartі клацатиClick me— число росте, прогрес рухається плавно, вікно перетягується. - У
threaded_task.pyзакрити вікно посеред роботи — застосунок завершується без повідомленняQThread: Destroyed while thread is still running.
Для звіту¶
Знімок екрана обох вікон одразу після натискання Start і кількох клацань Click me: у першому лічильник Click me (0), у другому — більший за нуль. Одним-двома реченнями пояснити, чому так.
Знайшли помилку чи бажаєте додати інформацію, щоб покращити курс? Створіть issue на GitHub