Python 3, Tkinter и многопоточный процесс, стиль ООП - PullRequest
1 голос
/ 11 декабря 2019

В моей маленькой программе потенциально длительный процесс. Это не проблема, когда вы делаете это с консоли, но теперь я хочу добавить графический интерфейс. В идеале я хочу использовать Tkinter (a), потому что это просто, и (b), потому что это может быть легче реализовать на разных платформах. Из того, что я прочитал и испытал, (почти) все GUI страдают от одной и той же проблемы. 1 - где основной рабочий процесс опрашивает (например, ожидает получения данных), и 2 - где рабочий процесс выполняет большую работу (например, копирует файлы в цикле for). Моя программа попадает в последнюю.

Мой код имеет "иерархию" классов.
Класс MIGUI обрабатывает графический интерфейс и взаимодействует с интерфейсным классом MediaImporter. Класс MediaImporter является интерфейсом между пользовательским интерфейсом (консоль или GUI) и рабочими классами. Класс Import является долговременным работником. Он не знает, что интерфейс или классы GUI существуют.

Проблема: после нажатия кнопки «Пуск» графический интерфейс блокируется, поэтому я не могу нажать кнопку «Прервать». Как будто я вообще не использую потоки. Я подозреваю, что проблема связана с тем, как я запускаю многопоточность в методе startCallback.

Я также пробовал подход многопоточности всего класса MediaImporter. Смотрите закомментированные строки.

import tkinter as tk
from tkinter import ttk
from tkinter import filedialog
import threading
import time


class MIGUI():
    def __init__(self, master):
        self.master = master

        self.mediaImporter = MediaImporter()

        self.startButton = ttk.Button(self.master, text='Start', command=self.startCallback)
        self.startButton.pack()

        self.abortButton = ttk.Button(self.master, text='Abort', command=self.abortCallback)
        self.abortButton.state(['disabled'])
        self.abortButton.pack()

    def startCallback(self):
        print('startCallback')
        self.abortButton.state(['!disabled'])
        self.startButton.state(['disabled'])
        self.abortButton.update()  # forcing the update seems unnecessary
        self.startButton.update()
        #print(self.startButton.state())
        #print(self.abortButton.state())

        self.x = threading.Thread(target=self.mediaImporter.startImport)
        self.x.start()
        self.x.join()

        #self.mediaImporter.startImport()

        self.startButton.state(['!disabled'])
        self.abortButton.state(['disabled'])
        self.abortButton.update()
        self.startButton.update()
        #print(self.startButton.state())
        #print(self.abortButton.state())

    def abortCallback(self):
        print('abortCallback')
        self.mediaImporter.abortImport()
        self.startButton.state(['!disabled'])
        self.abortButton.state(['disabled'])


class MediaImporter():
#class MediaImporter(threading.Thread):
    """ Interface between user (GUI / console) and worker classes """
    def __init__(self):
        #threading.Thread.__init__(self)

        self.Import = Import()
        #other worker classes exist too

    def startImport(self):
        print('mediaImporter - startImport')
        self.Import.start()

    def abortImport(self):
        print('mediaImporter - abortImport')
        self.Import.abort()


class Import():
    """ Worker
        Does not know anything about other non-worker classes or UI.
    """
    def __init__(self):
        self._wantAbort = False

    def start(self):
        print('import - start')
        self._wantAbort = False
        self.doImport()

    def abort(self):
        print('import - abort')
        self._wantAbort = True    

    def doImport(self):
        print('doImport')
        for i in range(0,10):
            #actual code has nested for..loops
            print(i)
            time.sleep(.25)
            if self._wantAbort:
                print('doImport - abort')
                return


def main():
    gui = True
    console = False

    if gui:
        root = tk.Tk()
        app = MIGUI(root)
        root.mainloop()
    if console:
        #do simple console output without tkinter - threads not necessary
        pass

if __name__ == '__main__':
    main()

1 Ответ

2 голосов
/ 11 декабря 2019

Причина, по которой ваш графический интерфейс заблокирован, заключается в том, что вы вызываете self.x.join(), который блокируется до завершения функции doImport, см. Документацию join . Вместо этого я бы вызвал join() в вашей функции abortCallback(), так как именно это приведет к остановке потока.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...