Мультиобработка для плавной работы графического интерфейса

#python #multithreading #user-interface #multiprocessing

Вопрос:

Я разработал графический интерфейс, который позволяет выполнять какое-то преобразование данных.

Каждое преобразование использует другой набор файлов и выполняется совершенно независимо от других задач. Чтобы приложение не зависало при выполнении задачи, я использовал модуль потоковой передачи, чтобы каждая операция выполнялась в отдельном потоке. Я не хочу ускорять вычисления с помощью многопоточности, мне просто нужно адаптивное приложение. Код, который у меня есть, действительно длинный (более 600 строк), поэтому я буду публиковать только соответствующие фрагменты кода, но могут возникнуть некоторые несоответствия из-за удаления больших разделов кода.

 import time
from threading import Thread
import tkinter as tk
from tkinter import Tk, ttk

retinaFileName = None
retinaProgBar = None

threadStatus = {'active_threads': [],
                'manager': True,
                'compile': False,
                'retina': False,
                'left_hemi':False,
                'right_hemi':False}

class layerGenerator:
    def __init__(self):
        pass

    def pack(self, thread):
        kernel = 0
        time.sleep(0.5)
        while kernel < 200 and threadStatus[thread]:
            time.sleep(0.01)
            kernel =1
        print("Done!")

def threadManager():
    while threadStatus['manager']:
        [t.join() for t in threadStatus['active_threads'] if not t.is_alive()]
        threadStatus['active_threads'] = [t for t in threadStatus['active_threads'] if t.is_alive()]
        time.sleep(0.1)
    print("Exiting manager")

def generateArrays(generator, btn, entry, progBar, cmd, thread):
    generator.pack(thread)
    threadStatus[thread] = False
    btn.config(text="Generate arrays", command=cmd)
    
def generateRet():
    t = Thread(target=generateArrays, args=(retina_generator, button6, retinaFileName, retinaProgBar, generateRet, 'retina'))
    t.start()
    threadStatus['retina'] = True
    threadStatus['active_threads'].append(t)
    button6.config(text="Abort", command=abortRet)
    
def abortRet():
    threadStatus['retina'] = False
    button6.config(text="Generate arrays", command=generateRet)
    
def on_closing():
    threadStatus['manager'] = False
    root.destroy()
    
retina_generator = layerGenerator()

root = Tk()
root.lift()
root.title("Retina Generator")
root.geometry('640x480')
root.resizable(False, False)

button6 = ttk.Button(root,text="Generate arrays", command=generateRet)
button6.pack()


manager = Thread(target=threadManager)
manager.start()

root.protocol("WM_DELETE_WINDOW", on_closing)
root.mainloop()
 

если вы запустите этот код, все кажется очень отзывчивым, но в моем реальном приложении кнопка прерывания обычно регистрируется как нажатая после завершения всего вычисления, что не очень полезно (я предполагаю, что процессор посвящает все свои тактовые циклы функции pack, которая является основной вычислительно дорогостоящей функцией. Только когда это будет сделано, он проверяет другие потоки, и в этот момент операция «прерывается»). Решит ли мультиобработка эту проблему? если да, то как я могу сообщить о событиях завершения, аналогичных while kernel < 200 and threadStatus[thread] линия? насколько мне известно, разные процессы работают с разными копиями памяти.

Опять же, я подчеркиваю: меня не волнует ускорение одной конкретной функции, скорее возможность запускать и отменять множество операций, сохраняя при этом гибкость пользовательского интерфейса.

P. S: Я подтвердил, что это просто проблема с циклами процессора, так как даже добавление очень небольшой задержки в цикл вычислений (например, печать ядра или даже time.sleep(0,001)) снова делает кнопку «прервать» отзывчивой, но я не хочу намеренно замедлять основные вычисления без причины.