【问题标题】:GUI Freezzes when opening a thread打开线程时 GUI 冻结
【发布时间】:2016-11-24 09:09:44
【问题描述】:

我确信这是你见过的最糟糕的代码之一,但这是我的第一个面向对象的程序。 该程序应与 Arduino 通信以收集有关太阳能电池板和一些电池的信息。它还必须自动管理一些逆变器等。 我已经删除了大部分 GUI 以使代码更易于阅读,但它仍然很大。 我尝试编写的代码是,一旦串行通信开始,我就可以更改 GUI 上的参数,我试图通过打开一个在后台工作并收集或发送数据的新线程来实现这一点。 实际发生的情况是,一旦串行通信启动,GUI 就会冻结,然后一切都会崩溃。 我在线程中添加了一个 print 来检查通信是否开始,并且实际上在 python chrashes 之前从串行端口收集了一些信息。

import Tkinter
import tkMessageBox
import ttk
import serial
import sys
import glob
import threading
from time import sleep


class PaginaPrincipale(Tkinter.Tk, threading.Thread):
    dati_in = None
    dati_out = None

    def __init__(self, parent):
        Tkinter.Tk.__init__(self, parent)
        self.parent = parent
        si1 = Tkinter.IntVar()
        au1 = Tkinter.IntVar()
        si2 = Tkinter.IntVar()
        au2 = Tkinter.IntVar()

        self.grid()

        # those classes will manage the auto function
        def manuale(variable):
            if variable == 1:
                print(si1.get())
            if variable == 2:
                print(si2.get())

        def automatico(variable):
            if variable == 1:
                print(au1.get())
            if variable == 2:
                print(au2.get())

        # this class manages the serial connection, it scans for the available ports
        # and when the user select the desired one it should open it and start a thread
        # I still haven't implemented the update of the GUI
        def connetti():

            # Here I extract the clicked value on the listbox
            def selezione(evt):
                w = evt.widget
                index = int(w.curselection()[0])
                value = w.get(index)
                scelta_box.config(text=value)

            # Here I try to open the selected port and to start a new thread which keeps exchanging
            # information with the microcontroller (Arduino)
            def avvia_seriale(porta):
                try:
                    print(porta)
                    pagina_connessione.destroy()
                    threading.Thread(target=comunicazione(porta))

                except:
                    # Here PiCharm gives me a warning: too broad exception clause
                    tkMessageBox.showerror('Serial port', 'Can''t open the selected serial port')
                    pass

            # here I will place all the serial communication statements
            def comunicazione(porta):
                porta_seriale = serial.Serial(porta)
                while porta_seriale.isOpen():
                    porta_seriale.write(1)
                    sleep(.1)
                    self.dati_in = porta_seriale.readline()
                    sleep(.1)
                    print self.dati_in
                pass

            # Here I scan for available ports and I put them inside the listbox
            if sys.platform.startswith('win'):
                ports = ['COM%s' % (i + 1) for i in range(256)]
            elif sys.platform.startswith('linux') or sys.platform.startswith('cygwin'):
                # this excludes your current terminal "/dev/tty"
                ports = glob.glob('/dev/tty[A-Za-z]*')
            elif sys.platform.startswith('darwin'):
                ports = glob.glob('/dev/tty.*')
            else:
                raise EnvironmentError('Unsupported platform')

            result = []
            for port in ports:
                try:
                    s = serial.Serial(port)
                    s.close()
                    result.append(port)  # il metodo append() aggiunge alla lista result l'ultimo termine trovato
                except (OSError, serial.SerialException):
                    pass

            # I open a new toplevel so that when I choose and open the serial port I close it and nothing remains
            # on the main page
            pagina_connessione = Tkinter.Toplevel()
            pagina_connessione.title('Gestione connessione')

            descrizione_scelte = Tkinter.Label(pagina_connessione, text='Lista scelte:', justify='left')
            descrizione_scelte.grid(column=0, row=0, sticky='W')
            lista_scelte = Tkinter.Listbox(pagina_connessione, height=len(result), selectmode='single')
            contatore = len(result)
            for item in result:
                lista_scelte.insert(contatore, item)
                contatore += 1

            if contatore == 0:
                lista_scelte.insert(0, 'Nessuna porta seriale')

            lista_scelte.grid(column=0, row=1)
            lista_scelte.bind('<<ListboxSelect>>', selezione)

            bottone_connessione = Tkinter.Button(pagina_connessione, text='Connetti!',
                                                 command=lambda: avvia_seriale(scelta_box.cget("text")))
            bottone_connessione.grid(column=1, row=1)

            scelta_box = Tkinter.Label(pagina_connessione, width=15, height=1, borderwidth=3, background='blue')
            scelta_box.grid(column=0, row=2)

            pagina_connessione.mainloop()

        #
        #
        # This is the main GUI
        #
        #
        frame_batteria1 = Tkinter.Frame(self, borderwidth=2, bg="black")
        frame_batteria1.grid(column=0, row=0, sticky='news')

        self.descrittore_v_b_1 = Tkinter.Label(frame_batteria1, text="V Batteria 1", font=("Helvetica", 8),
                                               justify='center')
        self.descrittore_v_b_1.grid(column=0, row=0, sticky='news')
        self.descrittore_i_b_1 = Tkinter.Label(frame_batteria1, text="I Batteria 1", font=("Helvetica", 8),
                                               justify='center')
        self.descrittore_i_b_1.grid(column=1, row=0, sticky='NEWS')

        self.vbatteria1 = Tkinter.Scale(frame_batteria1, bd=4, troughcolor='blue', resolution=0.1, state='disabled',
                                        from_=15, to=0)
        self.vbatteria1.grid(column=0, row=1, sticky='NEWS')
        self.ibatteria1 = Tkinter.Scale(frame_batteria1, bd=4, troughcolor='blue', resolution=0.1, state='disabled',
                                        from_=10, to=0)
        self.ibatteria1.grid(column=1, row=1, sticky='NEWS')

        self.descrittore_inverter1 = Tkinter.Label(self, text="Inverter 1", font=("Helvetica", 8), justify='left')
        self.descrittore_inverter1.grid(column=0, row=3, sticky='NEWS')

        self.scelte_manuali_inverter1 = Tkinter.Radiobutton(self, text="Acceso", variable=si1, value=1,
                                                            command=lambda: manuale(1))
        self.scelte_manuali_inverter1.grid(column=0, row=4, sticky='NEWS')
        self.scelte_manuali_inverter1 = Tkinter.Radiobutton(self, text="Spento", variable=si1, value=0,
                                                            command=lambda: manuale(1))
        self.scelte_manuali_inverter1.grid(column=0, row=5, sticky='NEWS')

        self.scelta_automatica_inverter1 = Tkinter.Checkbutton(self, text="Automatico", variable=au1, onvalue=1,
                                                               offvalue=0, command=lambda: automatico(1))
        self.scelta_automatica_inverter1.grid(column=2, row=4, sticky='NEWS')

        #
        #
        # separators
        #
        #
        ttk.Separator(self, orient='horizontal').grid(row=6, columnspan=8, sticky='EW')
        ttk.Separator(self, orient='vertical').grid(row=2, column=3, rowspan=4, sticky='NS')

        self.gestisci_connessione = Tkinter.Button(self, text="Connetti!", command=connetti)
        self.gestisci_connessione.grid(row=7, column=6, sticky='EW')


if __name__ == "__main__":
    applicazione = PaginaPrincipale(None)
    applicazione.title('Pannello di controllo')
    applicazione.mainloop()

【问题讨论】:

  • 您不能使用 while 循环,因为它会阻止 mainloop 在 Tkinter(和任何其他 GUI)中执行任何操作 - 它获取键/鼠标事件,将其发送到小部件,更改数据小部件,并重绘小部件。您可以使用root.after(millisecond, function_name) 定期运行某些功能并“模拟”while 循环。或者您可以在循环中使用root.update() 来强制 mainloop 执行一个循环。
  • 但是如果我将它放在一个新线程中,为什么 while 循环会阻塞 mailnoop?如果我从理论上理解您的命令的答案,我可以删除线程吗?
  • 简单示例:read serial in tkinter

标签: python multithreading user-interface arduino serial-communication


【解决方案1】:

您以错误的方式运行线程。现在你有

threading.Thread(target=comunicazione(porta))

但是target= 需要函数名——这意味着没有() 和参数。

所以你在主线程中作为普通函数运行comunicazione(porta),当函数返回一些东西时,它将被分配给target=。但是函数永远不会停止和阻塞主线程。

您可以使用lambda 创建不带参数的函数并分配给变量。

threading.Thread(target=lambda:comunicazione(porta))

但是你必须使用your_thread.start()来运行线程ie。

t = threading.Thread(target=lambda:comunicazione(porta))
t.start()

【讨论】:

  • 现在可以了。谢谢!我应该注意avvia_seriale(porta) 异常的警告还是可以忽略它?
  • 你至少可以print() 它——有时它可能是有用的信息。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-12-22
  • 2012-05-12
  • 2010-09-20
  • 1970-01-01
  • 2015-11-21
相关资源
最近更新 更多