Tkinter (модуль ttk) и работающая функция в фоновом режиме, приводящая к сбою программы

У меня серьезная проблема с использованием Tkinter (модуль ttk) и запуском функции в фоновом режиме и получением print(sys.stdout) сообщений функции в текстовый виджет GUIself.constext. Сама функция содержит классы, которые выполняют пространственные запросы, занимающие около 15 минут, а между ними есть операторы печати для проверки состояния расчета. Пока этот пост не был отредактирован, я пробовал использовать threading, Queue, реализуя ThreadingClient или QueueClient, но запуск графического интерфейса всегда приводил к сбою программы.

Итак, вот мой код до сих пор

    import tkFileDialog
    import tkMessageBox
    import ttk
    import Tkinter
    import Queue
    import os
    import time
    import sys
    import threading
    import multiprocessing
    import gemeindesteckbrief__SupportTools__




   class SystemInfoSupport():

   #def __init__(self, master,factshHW, factshHwGeb,factshHwSch, factshGem,
   factshGeol, factshWLV) :
       def __init__(self,master):
   #Actual Window
    self.sysInf =ttk.Frame (master)
    self.sysInf.grid()
    self.sysInf.grab_set()
    self.incrVal = 0
    #self.__calcFacthw = factshHW
    #self.__calcFacthwGeb = factshHwGeb
    #self.__calcFactHwSch = factshHwSch
    #self.__calcFactGeol = factshGeol
    #self.__calcFactWLV = factshWLV
    #self.__calcFactGem = factshGem

    #print self.__calcFacthwGeb



    self.style= ttk.Style()
    self.style.configure("Head.TLabel",foreground="#20B2AA", background="#E6E6FA", font = "Verdana 12 bold")
    self.headLabel= ttk.Label (self.sysInf,text = "Systeminformation- Kalkulation",style = "Head.TLabel")
    self.headLabel.grid(row=0, column =0, sticky ="NW",pady = 15, padx =20)


    #Process OVerview
    self.mainFrame = ttk.LabelFrame (self.sysInf,width=200,height=100)
    self.mainFrame.grid(row=2, column =0, sticky = "NW", padx = 15, pady = 5)
    self.style.configure("Prog.TLabel", font = "Verdana 10 italic underline")
    self.progLabel= ttk.Label (self.mainFrame,text = "Räumliche Analysen-Fortschritt:", style ="Prog.TLabel")
    self.progLabel.grid (row =2,column =1, sticky = "NW", padx = 10, pady = 2)
    self.progBar= ttk.Progressbar(self.mainFrame,mode='determinate',length = 370, name='progBar1')
    self.progBar.grid(row=3,  column=0, columnspan=4,sticky ="NW", pady=5, padx=10)
    self.style.configure("Scale.TLabel", font = "Verdana 8 bold")
    self.scaleBounds = ttk.Label (self.mainFrame,text = "0 %\t\t\t\t\t    100 %")
    self.scaleBounds.grid (row =4,column =1, sticky = "NW", padx = 5, pady = 1)

    self.textFrame = ttk.LabelFrame (self.mainFrame,width=200,height=100)
    self.textFrame.grid(row=5, column =1, sticky = "NW", padx = 5, pady = 10)
    self.style.configure("Consol.TLabel", font ="Verdana 8 bold")
    self.consLable = ttk.Label (self.textFrame,text = "Log-Console:",style ="Consol.TLabel")
    self.consLable.grid (row =6,column =1, sticky = "NW", padx = 5, pady = 1)
    self.consText= ttk.Tkinter.Text(self.textFrame, wrap = "word")
    self.consText.grid(row =7,column =1, rowspan =4)
    self.consText.tag_configure("stderr", foreground="#b22222")
    self.scrollText= ttk.Scrollbar(self.textFrame,command = self.consText.yview)
    self.scrollText.grid(row =7,column =2,rowspan =4,sticky='NSEW')
    self.consText.config(yscrollcommand = self.scrollText.set)

    self.cancelButton = ttk.Button (self.mainFrame, text ="Abbrechen",command = self.testProgBar)
    self.cancelButton.grid (row =12,column =1)


    sys.stdout = TextRedirector(self.consText, "stdout")
    sys.stderr=  TextRedirector(self.consText, "stderr")


    # Create new threads
    # run function in background using a ThreadingClient
   # self.thread1 = gemeindesteckbrief_SpatialThread.SpatialThread(self.__calcFacthw, self.__calcFacthwGeb)

    #run function in a threading.Thread
    #self.thread1 = threading.Thread(name ="MyThread", target = self.prozessCalculateFactsheets)
    #run function in a threading.Timer
    #self.thread1= threading.Timer(2,self.calculateFactsheets)
    #Start the thread
    #self.thread1.start()

    #self.check_thread()

#Check if thread is still executing or not
def check_thread(self):
# Still alive? Check again in half a second
    if self.thread1.isAlive():
        self.sysInf.after(500,self.check_thread)


# function to test the sys.stdout behaviour and writting to the Tkinter.text widget
def testProgBar (self):
  print "hello my friend"
  sys.stderr.write("hello my error friend\n")
  self.sysInf.grab_release()


# the actual  function needed to be executed in background
def calculateFactsheets (self):
    # Read the directory to the input data of the init_File and add to a new Factsheet spatial calculation
    try:
        print "''''Hello from the Calculation Function ()''''"
        requireData = gemeindesteckbrief__SupportTools__.ToolSet()

        if self.__calcFacthw == 1:
            factsheetHochw = gemeindesteckbrief_SpatialCalculFactshHochw.SpatialAnalysis_FactsheetHochwasser(requireData.readData(13),requireData.readData(10),
                            requireData.readData(20), requireData.readData(21),  requireData.readData(22),  requireData.readData(23),  requireData.readData(24),
                            requireData.readData(25),  requireData.readData(26))

            factsheetHochw.verkExpertAnalyseGZPBWV()
            factsheetHochw.verkExpertAnalyseHSG()
            factsheetHochw.verkExpertAnalyseTotal()
            factsheetHochw.verkExpertAnalyseGZPOI()
            factsheetHochw.verkExpertAnalyseGZLOI()
            factsheetHochw.verkExpertAnalyseHSGPOI()
            factsheetHochw.verkExpertAnalysePLOITot()
            factsheetHochw.verkExpertAnalyseLandWald()
            factsheetHochw.verkExpertAnalyseLandWaldTotal()

        if self.__calcFacthwGeb == 1:
            print "FACTSHEET HOCHWASSER Gebaeude startet"
            factsheetHochwGeb = gemeindesteckbrief_SpatialCalculFactshHochwGebaeude.SpatialAnalysis_FactsheetHochwGebaeude(requireData.readData(13),requireData.readData(10),
                        requireData.readData(16),requireData.readData(15), requireData.readData(17),requireData.readData(18))
            print "Data correct initialized"
            factsheetHochwGeb.gebaeudeExpAnalyseGZPBWV()
            factsheetHochwGeb.gebaeudeExpAnalyseHSG()
            factsheetHochwGeb.gebaeudeExpAnalyseTotal()

        tkMessageBox.showinfo("Räumlicher-Analyse Erfolgreich","Die Berechnungen wurden erfolgreich abgeschlossen!")

    except:
        tkMessageBox.showerror ("FactsheetHochwasser_Gebaeude FEHLER","Bei der Berechnung ist ein Fehler aufgetreten!\n Für Details öffnen Sie das Error-File in der Programmumgebung")


class TextRedirector(object):
def __init__(self,widget, tag):
    self.targetwidget = widget
    self.targettag = tag

#@Override the sys.stdout & sys.stderr methods to write to the text widget instead of the python console
def write(self, str):
    self.targetwidget.configure(state="normal")
    self.targetwidget.insert("end", str, (self.targettag,))
    self.targetwidget.configure(state="disabled")


   root = ttk.Tkinter.Tk()
   root.title ("SystemINFO-Menü")
   runGUI = SystemInfoSupport (root)
   root.mainloop () 

В основном мне нужно выполнить функцию def calculateFactsheets (self): в фоновом режиме и получить сообщения консоли печати или ошибки, чтобы записать их в виджет.

Любые идеи??


person freeski_52    schedule 26.07.2013    source источник
comment
ваш отступ перепутан, что затрудняет чтение кода.   -  person Bryan Oakley    schedule 26.07.2013
comment
благодаря объявлению исходного кода этой веб-страницы ... да, но я надеюсь, что это не так уж плохо!   -  person freeski_52    schedule 29.07.2013
comment
Вам следует потратить время на исправление отступа, если вы ожидаете, что люди найдут время, чтобы ответить на ваш вопрос.   -  person Bryan Oakley    schedule 29.07.2013


Ответы (2)


Tkinter не является потокобезопасным. Если вы попытаетесь вставить данные в текстовый виджет, вы получите непредсказуемое поведение (или довольно часто сбой). Чтобы отдельный поток отправлял данные в виджет, вам нужно записать данные в потокобезопасную очередь, а затем ваш основной поток опрашивает эту очередь (используя метод tkinter after).

также tkMessagebox не может быть вызван из потока (сбои)

см. здесь

person DCA-    schedule 26.07.2013
comment
Я знаю, что Tkinter не является потокобезопасным! Я только что прочитал много материала о Tkinter и Threads, но любое решение действительно решило мою настоящую проблему (обработчик очереди, обработчик потоков и т. д.) - person freeski_52; 26.07.2013

У меня похожая проблема, которую я решил так:

class UpdateWindow(Toplevel):
     def __init__(self):
        Toplevel.__init__(self)
        self.create_widgets()
        self.grid()
        self.focus_force()

     def create_widgets(self):
        self.queue = Queue.Queue()
        #...
        self.button= Button(self, text='Auto Update', command=lambda: self.spawnthread(function))
        self.button.grid(row=1, column=0, sticky=N)
        #... 

     def spawnthread(self, fcn):
        self.button.config(state="disabled")
        self.thread = ThreadedClient(self.queue, fcn)
        self.thread.start()
        self.periodiccall()

    def periodiccall(self):
        if self.thread.is_alive():
            self.after(100, self.periodiccall)
            self.progressbar.step(500)
        else:
            self.button.config(state="active")
            self.progressbar.stop()


class ThreadedClient(threading.Thread):
    def __init__(self, queue, fcn):
        threading.Thread.__init__(self)
        self.queue = queue
        self.fcn = fcn

    def run(self):
        time.sleep(1)
        self.queue.put(self.fcn())

В этом примере у меня есть индикатор выполнения, но у вас не должно возникнуть проблем с его адаптацией к вашему коду, в основном путем изменения periodiccall.

person Al.Sal    schedule 26.07.2013
comment
в лямде: ссылка в вашем коде self.spawnthread вызывается с сигнатурой функции... что именно делает функция? Можно ли добавить мою функцию def.calculateFactsheets() на эту позицию? - person freeski_52; 29.07.2013
comment
Да. В моем исходном коде у меня была реальная функция; Я добавил function в качестве заполнителя. Здесь вы бы разместили функцию, которая будет в другом потоке. - person Al.Sal; 29.07.2013