Грамотно завершить работу приложения tkinter (включая многопоточность, многопроцессорность, QueueHandler, QueueListener)Python

Программы на Python
Ответить Пред. темаСлед. тема
Anonymous
 Грамотно завершить работу приложения tkinter (включая многопоточность, многопроцессорность, QueueHandler, QueueListener)

Сообщение Anonymous »

Вопрос
Как заставить приложение Python tkinter корректно закрываться в конце нормальной работы, если пользователь закрывает окно, прерывая выполняемую обработку?
Подробнее
Приложение tkinter запускает долговременную функцию long.work() при нажатии кнопки, эта функция запускается в новом потоке, что позволяет приложению tkinter реагировать во время длительного выполнения.
Некоторые функции, вызываемые в work(), являются многопоточными через ThreadPoolExecutor< /код> (concurrent_tasks.do_multi_threads()), а другие обрабатываются в многопроцессорном режиме с помощью joblib Parallel() и Delay() (parallel_tasks.do_multi_process()).
Поэтому плавное завершение должно обрабатывать случаи, когда завершение пользователя не происходит, завершение пользователя происходит после завершения функции, прерывание пользователя происходит в многопоточном и многопроцессном состояниях, а также обрабатывать закрытие средств ведения журнала, которые используют QueueListener и QueueHandler для записи сообщений журнала в файл журнала.
Я рассмотрел несколько подобных вопросов здесь, на SO, но ни одного охватить все необходимые варианты использования.
Пример
Мой подход до сих пор выдает ошибку при закрытии окна, прерывая многопоточную функцию (среди прочего):< /p>
app.py
import logging
from logging.handlers import QueueHandler, QueueListener
import sys
import multiprocessing
import threading
import tkinter as tk
from tkinter import Tk, ttk
from joblib.externals.loky import get_reusable_executor

import long

def finish(
logger: logging.Logger,
listener: QueueListener = None,
log_queue: multiprocessing.Queue = None,
threads: bool = False,
lock: threading.Lock = None,
root: tk.Tk = None,
exit_code: int = 0,
):
# QueueHandler
if log_queue is not None:
log_queue.put_nowait(None)

# QueueListener
if listener is not None:
listener.stop()

# joblib
get_reusable_executor(kill_workers=True).shutdown(wait=True)

# multiprocessing
for p in multiprocessing.active_children():
p.terminate()
p.join()

# threading
if threads is True:
for thread in threading.enumerate():
if thread is not threading.current_thread():
thread.join(timeout=1)

# logger
for handler in logger.handlers:
handler.flush()
handler.close()
logger.handlers.clear()

# lock
if lock is not None and lock.locked():
lock.release()

# tkinter
if root is not None:
root.destroy()
sys.exit(exit_code)

def main():
root = Tk()

# logger
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
log_fmt = logging.Formatter(
"%(asctime)s (%(levelname)s) %(message)s", datefmt="%H:%M:%S"
)
file_handler = logging.FileHandler("log_file.log")
file_handler.setFormatter(log_fmt)
file_handler.setLevel(logging.DEBUG)

# note also a StreamHandler is added to QueueListener

manager = multiprocessing.Manager()
log_queue = manager.Queue()
queue_handler = QueueHandler(log_queue)
logger.addHandler(queue_handler)

listener = QueueListener(log_queue, file_handler, respect_handler_level=True)
listener.start()

def begin():
# entry_vals = get Entry values...
exit_code = 0
lock = threading.Lock()

def terminate():
finish(logger, listener, log_queue, threads=True, lock=lock, root=root, exit_code=exit_code)

root.protocol("WM_DELETE_WINDOW", terminate)

def start(): # EDIT
try:
long.work(entry_vals)
finally:
button.config(state=tk.NORMAL) # re-enable
finish(logger, listener, log_queue, threads=True, lock=lock, root=None, exit_code=exit_code)

try: # EDIT (indentation)
set_progress = entry_vals.get("update_status")
if set_progress: # EDIT
set_progress("begin", 1) # EDIT
logging. Debug("Starting GUI...")
button.config(state=tk.DISABLED) # disable button
threading.Thread(target=start).start() # EDIT
except Exception as e:
exit_code = 1
logging.exception(e)
finish(logger, listener, log_queue, threads=False, lock=None, root=None, exit_code=exit_code)

frame = ttk.Frame(root)
frame.grid(row=0, column=0, sticky="ew", padx=5, pady=5)
button = ttk.Button(frame, text="Start", command=begin)

root.mainloop()

if __name__ == "__main__":
main()

long.py
import logging
from concurrent. Futures import ThreadPoolExecutor
from functools import partial
from joblib import Parallel, delayed

import concurrent_tasks
import parallel_tasks

def work(entry_vals):
set_progress = entry_vals.get("update_status")
partial_func = partial(concurrent_tasks.do_multi_threads, entry_vals)
if set_progress: # EDIT
set_progress("work", 10) # EDIT
logging.debug("Starting multithreaded function...") # EDIT
with ThreadPoolExecutor() as executor:
res_1 = list(executor.map(partial_func, range(10)))

if set_progress: # EDIT
set_progress("work", 60) # EDIT
logging.debug("Starting multiprocessed function...") # EDIT
with Parallel(n_jobs=-1) as parallel_work:
res_2 = list(parallel_work(delayed(parallel_tasks.do_multi_process)(entry_vals, i) for i in range(10)))

return res_1, res_2

Дополнительная информация
Это изменение дополняет упрощенное представление кода, приведенное выше: Существует некоторое функциональное программирование, которое позволяет увеличивать tk.ttk.Progressbar< /code> путем установки tk.IntVar, который является аргументом переменной параметра Progressbar.
app.pydef update_status(
progress_var,
progress_bar,
percent_label,
status_label,
lock
):
def callback(section, percent):
with lock:
if percent is not None:
progress_var.set(percent)
percent_label["text"] = f"{progress_var.get()}%"
status_label["text"] = section

progress_bar.update_idletasks()
percent_label.update_idletasks()
status_label.update_idletasks()

progress_bar.after(10000, callback)
return callback

Ошибка
Exception has occurred: RuntimeError
main thread is not in main loop

in start:
long.work(entry_vals)

in work:
set_progress("work", 60)

in callback:
progress_var.set(percent)
RuntimeError: main thread is not in main loop

During handling of the above exception, another exception occurred:
in start:
button.config(state=tk.NORMAL)
RuntimeError: main thread is not in main loop


Подробнее здесь: https://stackoverflow.com/questions/791 ... essing-que
Реклама
Ответить Пред. темаСлед. тема

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

  • Похожие темы
    Ответы
    Просмотры
    Последнее сообщение

Вернуться в «Python»