Python Сбои tkinter часто обновляют элемент ProgressBar - PullRequest
0 голосов
/ 18 марта 2020

Windows 10 с Python 2.7.13 (32 бита)

У меня есть программа Python tkinter с индикатором выполнения. Программа выполняет длительную операцию, а затем ждет заданное количество времени, прежде чем повторить ее.

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

Через 15-30 минут, во время обратного отсчета с использованием приведенного ниже кода, Windows выскакивает python .exe перестал работать диалог , предлагающий только возможность закрыть программу, и программа не отвечает (предположительно потому, что Windows остановил ее).

Я не могу понять, почему это происходит. В течение этого времени в GUI выполняется следующий сегмент кода:

def wait_nextop(self, delay):
    end = time.time() + delay

    self.progress_bar.stop()
    self.progress_bar.config(mode = 'determinate', max = delay)

    while time.time() < end:
        remaining = (end - time.time()) + 1
        self.progress_bar.config(value = (delay - remaining))
        remaining_text = str(datetime.timedelta(seconds = int(remaining)))
        self.progress_text.config(text = 'Next operation in ' + remaining_text)
        time.sleep(0.1)

    self.progress_text.config(text = 'Operation in progress')    
    self.progress_bar.config(mode = 'indeterminate')
    self.progress_bar.start()

При этом delay является целочисленной задержкой в ​​секундах. self.progress_bar является экземпляром ttk.ProgressBar и self.progress_text является экземпляром tkinter.Label . time и datetime взяты из стандартных библиотек.

Python не предлагает трассировки стека, и в настоящее время у меня нет других доступных систем для тестирования этого. Следует отметить, что эта GUI функция вызывается другим потоком, но предназначена для выполнения в основном потоке.

Я видел подобные вопросы, но не смог найти рабочее разрешение:

1 Ответ

0 голосов
/ 19 марта 2020

Судя по советам из комментариев, мне кажется, что я должен использовать систему событий tkinter, а не вызывать функции непосредственно в потоке GUI.

Вот рабочий пример для Python2 .7 используя описанные понятия. Он создает GUI с индикатором выполнения, а затем обновляет его из другого потока, используя систему событий.

## Import the required modules
import threading
import Tkinter as tk
import ttk
import time
import random

## Class for creating the GUI
class GUI():
    def __init__(self):
        ## Make the GUI
        self.root = tk.Tk()        
        self.progress = ttk.Progressbar(self.root, orient = 'horizontal', length = 100, mode = 'determinate', max = 10)

        ## Bind an event to trigger the update
        self.root.bind('<<MoveIt>>', self.update_progress)

        ## Pack the progress bar in to the GUI
        self.progress.pack()

    def run(self):
        ## Enter the main GUI loop. this blocks the main thread
        self.root.mainloop()

    ## Updates the progress bar
    def update_progress(self, event):
        ## Work out what the new value will be
        new_value = self.progress['value'] + 1

        ## If the new value is less than 10..
        if new_value < 10:
            print('[main thread] New progress bar value is ' + str(new_value))
            ## Update the progress bar value
            self.progress.config(value = new_value)
            ## Force the GUI to update
            self.root.update()
        ## If the new value is more than 10
        else:
            print('[main thread] Progress bar done. Exiting!')
            ## Exit the GUI (and terminate the script)
            self.root.destroy()

## Class for creating the worker thread
class Worker(threading.Thread):
    def __init__(self, ui):
        ## Run the __init__ function from our threading.Thread parent
        threading.Thread.__init__(self)

        ## Save a local reference to the GUI object
        self.ui = ui

        ## Set as a daemon so we don't block the program from exiting
        self.setDaemon(True)

    def run(self):        
        print('[new thread] starting')
        ## Count to 10
        for i in range(10):
            ## Generate an event to trigger the progress bar update
            ui.root.event_generate('<<MoveIt>>', when = 'tail')

            ## Wait between one and three seconds before doing it again
            wait = random.randint(1,3)
            print('[new thread] Incrementing progress bar again in ' + str(wait) + ' seconds')
            time.sleep(wait)

## Create an instance of the GUI class            
ui = GUI()
## Create an instance of the Worker class and tell it about the GUI object
work = Worker(ui)
## Start the worker thread
work.start()
## Start the GUI thread (blocking)
ui.run()
...