Как распараллелить методы при использовании Tkinter - PullRequest
0 голосов
/ 24 мая 2018

Как запустить два метода одновременно, если один из них использует объекты Tkinter?

Настройка проблемы: Метод A останавливает метод B по истечении заданного времени.До этого метод A отображает оставшееся время в метке Tkinter.

Проблема: Методы не запускаются одновременно.

Версия Python: 2.7

ОС: Windows 7

Я использую потоки для реализации параллелизма.Я читал, что в Python есть нечто, называемое Global Interpreter Lock, которое заставляет потоки работать последовательно.Я предполагаю, что это вызывает проблему.

Обходной путь должен был бы использовать Процессы.Это невозможно, поскольку объекты Tkinter не могут быть превращены в символьные потоки («pickle»).Я получаю эту ошибку, когда пытаюсь использовать процессы: PicklingError: Невозможно выбрать объект 'tkapp'.

Приведенный ниже пример запуска имитирует реальную программу, которая намного больше.По этой причине он использует шаблон проектирования Model-View-Controller.Я скопировал некоторый код из Тайм-аут при вызове функции .

Вариант использования: Пользователь нажимает кнопку.Это запускает фоновую задачу, которая может занять много времени.Во время выполнения фоновой задачи интерфейс постоянно сообщает пользователю, сколько времени осталось до тех пор, пока программа не отменит фоновую задачу.

Поток, работающий в фоновом режиме, не реализован, поэтому его можно остановить.Но это не то, что мне интересно, так или иначе.

from Tkinter import *
from time import sleep
from threading import Thread, Timer

class Frontend(Tk):

    def __init__(self):

        Tk.__init__(self)

        self.label = Label(self, text = "", font = ("Courier", 12))
        self.button = Button(self, text = "Run thread in background.", font = ("Courier", 12))
        self.label.grid()
        self.button.grid(sticky = "nsew")

class Backend:

    def background_task(self):

        print "Background task is executing."
        sleep(6)
        print "Finished."

class Controller:

    def __init__(self):

        self.INTERRUPT_AFTER = 4
        self.done = True
        self.backend = Backend()
        self.frontend = Frontend()
        self.frontend.button.configure(command = self.run_something_in_background)

    class Decorator(object):

        def __init__(self, instance, time):

            self.instance = instance
            self.time = time

        def exit_after(self):
            def outer(fn):
                def inner():

                    timer = Timer(self.time, self.quit_function)
                    timer.start()
                    fn()

                    return timer
                return inner
            return outer

        def quit_function(self):

            if not self.instance.done:
                self.instance.display_message("Interrupted background task.")
                self.instance.set_done(True)

    def run_something_in_background(self):

        backendThread = Thread(target = self.backend.background_task)
        decorator = self.Decorator(self, self.INTERRUPT_AFTER)
        countdown = decorator.exit_after()(self.countdown)    # exit_after returns the function with which we decorate.

        self.set_done(False)
        countdown() 
        backendThread.start()
        backendThread.join()
        self.set_done(True)


    def countdown(self):

        seconds = self.INTERRUPT_AFTER
        while seconds > 0 and not self.done:
            message = "Interrupting background task in {} seconds\nif not finished.".format(str(seconds))
            self.display_message(message)
            seconds -= 1
            sleep(1)

    def set_done(self, val):

        self.done = val

    def display_message(self, message):

        self.frontend.label.config(text = message)
        self.frontend.update_idletasks()

    def run(self):

        self.frontend.mainloop()


app = Controller()
app.run()

1 Ответ

0 голосов
/ 25 мая 2018

Проблема, которую вы будете пытаться использовать как многопоточную, так и многопроцессорную, заключается в том, что на цикл событий tkinter, похоже, влияет внутренний поток / процесс.Что вы можете сделать, это то, что я сделал здесь.Ключ использует subprocess.Popen ().Это заставляет интерпретатор открыть другой интерпретатор, который не загрузил tkinter и не запускает основной цикл (убедитесь, что вы этого не сделали).

Это программа frontend.py:

from Tkinter import *
from subprocess import Popen

class Frontend(Tk):

    def __init__(self):

        Tk.__init__(self)
        self.label = Label(self, text = "", font = ("Courier", 12), justify='left')
        self.button = Button(self, text = "Run thread in background.", font = ("Courier", 12))
        self.label.grid()
        self.button.grid(sticky = "nsew")

class Controller:

    def __init__(self):

        self.INTERRUPT_AFTER = 4
        self.done = False
        self.frontend = Frontend()
        self.frontend.button.configure(command = self.run_something_in_background)

    def run_something_in_background(self, *args):

        self.set_done(False)
        seconds = 4
        self.frontend.after(int(seconds) * 1000, self.stopBackend)
        self.countdown(seconds) 

        self.backendProcess = Popen(['python', 'backend.py'])

    def stopBackend(self):
        self.backendProcess.terminate()
        self.done = True
        print 'Backend process terminated by frontend.'
        self.display_message('Backend process terminated')

    def countdown(self, remaining):
        print 'countdown', remaining
        if remaining > 0:
            message = "Interrupting background task in"
            message += " {} seconds\nif not finished.".format(str(remaining))
        elif self.done:
            message = 'Backend process completed'
        self.display_message(message)
        remaining -= 1
        if remaining > 0 and not self.done:
            self.frontend.after(1000, lambda s=remaining: self.countdown(s))
        else:
            message = 'Interrupting backend process.'
            self.display_message(message)

    def set_done(self, val):
        self.done = val

    def display_message(self, message):
        self.frontend.label.config(text = message)
        self.frontend.update_idletasks()

    def run(self):
        self.frontend.mainloop()

app = Controller()
app.run()

И код backend.py:

from time import sleep


def background_task():

    print "Background task is executing."
    for i in range(8):
        sleep(1)
        print 'Background process completed', i+1, 'iteration(s)'
    print "Finished."

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