Грамотно завершить работу приложения 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)

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:
logging.debug("Starting GUI...")
button.config(state=tk.DISABLED) # disable button
threading.Thread(target=begin).start()
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
from concurrent.futures import ThreadPoolExecutor
from functools import partial
from joblib import Parallel, delayed

import concurrent_tasks
import parallel_tasks

def work(entry_vals):
partial_func = partial(concurrent_tasks.do_multi_threads, entry_vals)
# do work
with ThreadPoolExecutor() as executor:
res_1 = list(executor.map(partial_func, range(10)))

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


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

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

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

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

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

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

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