执行长函数时如何使 Tkinter GUI 不崩溃?
How to make the Tkinter GUI not crash when executing a long function?
我目前正在开发一个程序,该程序使用 Python 和 Tkinter 构建,用于与我们实验室中的不同仪器交互并测试一些电子设备。我有一个函数 (isDutAlive()),它可能需要很长时间(最多 10-15 分钟)才能完成。
现在,Tkinter GUI 在此期间没有响应,对用户来说,程序似乎崩溃了。也没有其他操作是可能的。
这是代码:
import os
import sys
import requests
import pyvisa
import time
from datetime import datetime
import threading
from jsonrpcclient import request
from tkinter import *
from tkinter import ttk
print("-----------------------------------------")
print(" Q-Center V0.1 ")
print("-----------------------------------------")
port = ":8080"
rm = pyvisa.ResourceManager()
def listArticles():
#print("PK IP Address: ")
#pkIpAddress = input()
response = request("http://" + ipEntry.get() + port, "temperature_test_list_articles")
print(response.text)
print(response.data.result)
answer = response.data.result
pkReply.insert(END, answer)
pkReply.insert(END, '\n\n')
def setArticle():
response = request("http://" + ipEntry.get() + port, "temperature_test_set_article", articleEntry.get())
print(response.text)
print(response.data.result)
answer = response.text
pkReply.insert(END, answer)
pkReply.insert(END, '\n\n')
def unsetArticle():
response = request("http://" + ipEntry.get() + port, "temperature_test_unset_article")
print(response.text)
print(response.data.result)
answer = response.text
pkReply.insert(END, answer)
pkReply.insert(END, '\n\n')
def clearLog():
pkReply.delete('1.0', END)
def listInstruments():
instruments = rm.list_resources()
print(instruments)
print('\n\n')
visaReplyText.insert(END, instruments)
visaReplyText.insert(END, '\n\n')
return 0
def isDutAlive():
timestamp = time.strftime("%Y_%m_%d-%H_%M_%S.txt")
logFile = open(timestamp, "a")
logFile.write("Timestamp DUT Result Voltage Current\n\n")
for x in range (int(firstSocketEntry.get()), int(lastSocketEntry.get())+1):
print(x)
time.sleep(1)
response = request("http://" + ipEntry.get() + port, "temperature_test_is_dut_alive", x)
time.sleep(1)
print(response.text)
print(response.data.result)
answer = response.data.result
pkReply.insert(END, answer)
pkReply.insert(END, '\n\n')
logFile.write(time.strftime("%Y_%m_%d-%H_%M_%S\t\t") + str(x) + "\t\t" + str(answer) + "\t\t" + str(readVoltage()) + "\t\t" + str(readCurrent()) + "\n")
print("TEST")
def clearPyvisaLog():
visaReplyText.delete('1.0', END)
return 0
def readVoltage():
choosenInstrument = rm.open_resource(instrumentEntry.get())
voltageMeasurement = choosenInstrument.query_ascii_values("MEAS:VOLT? (@" + setChannelEntry.get() + ")")
visaReplyText.insert(END, voltageMeasurement)
visaReplyText.insert(END, " V")
visaReplyText.insert(END, "\n\n")
return voltageMeasurement
def readCurrent():
choosenInstrument = rm.open_resource(instrumentEntry.get())
currentMeasurement = choosenInstrument.query_ascii_values("MEAS:CURR? (@" + setChannelEntry.get() + ")")
visaReplyText.insert(END, currentMeasurement)
visaReplyText.insert(END, " A")
visaReplyText.insert(END, "\n\n")
return currentMeasurement
def setVoltage():
choosenInstrument = rm.open_resource(instrumentEntry.get())
print(choosenInstrument.query('*IDN?'))
print(choosenInstrument.write("VOLT:LEV " + setVoltageEntry.get() + ", (@" + setChannelEntry.get() + ")"))
return 0
def setCurrent():
choosenInstrument = rm.open_resource(instrumentEntry.get())
print(choosenInstrument.query('*IDN?'))
print(choosenInstrument.write("CURR:LEV " + setCurrentEntry.get() + ", (@" + setChannelEntry.get() + ")"))
return 0
def toggleChannelOutput():
choosenInstrument = rm.open_resource(instrumentEntry.get())
#choosenInstrument.write("OUTPut OFF, (@2)")
choosenInstrument.write(output.get() + ", (@" + setChannelEntry.get() + ")")
return 0
def sendRelayCommand():
send = requests.get('http://' + relayIPEntry.get() + '/io.cgi?DO' + on_off.get() + relayNumberEntry.get())
print(on_off.get())
def cycleRelay():
send = requests.get('http://' + relayIPEntry.get() + '/io.cgi?DOI' + relayNumberEntry.get() + '=100')
root = Tk()
#root.geometry("800x400")
root.title("Q-Center")
root.iconbitmap("./icons/icon.ico")
root.call('wm', 'iconphoto', root._w, PhotoImage(file='./icons/icon.png'))
# root.iconphoto(False, PhotoImage(file='./icons/icon.png'))
on_off = StringVar()
output = StringVar()
##############################################################################################################################
tabControl = ttk.Notebook(root)
tab1 = ttk.Frame(tabControl)
tab2 = ttk.Frame(tabControl)
tab3 = ttk.Frame(tabControl)
tabControl.add(tab1, text="PK Control")
tabControl.add(tab2, text="Keysight Control")
tabControl.add(tab3, text="Relay Board Control")
tabControl.pack(expand=1, fill="both")
##############################################################################################################################
ipEntry = ttk.Entry(tab1)
ipEntry.pack()
ipEntry.insert(0, "192.168.1.73")
listArticlesButton = Button(tab1, text="List Articles", command=listArticles)
listArticlesButton.pack()
articleEntry = ttk.Entry(tab1)
articleEntry.pack()
setArticleButton = Button(tab1, text="Set Article", command=setArticle)
setArticleButton.pack()
unsetArticleButton = Button(tab1, text="Unset Article", command=unsetArticle)
unsetArticleButton.pack()
firstSocketTestLabel = Label(tab1, text="Enter First Socket To Test")
firstSocketTestLabel.pack()
firstSocketEntry = ttk.Entry(tab1)
firstSocketEntry.pack()
lastSocketTestLabel = Label(tab1, text="Enter Last Socket To Test")
lastSocketTestLabel.pack()
lastSocketEntry = ttk.Entry(tab1)
lastSocketEntry.pack()
isDutAliveButton = Button(tab1, text="Is DUT Alive", command=isDutAlive)
isDutAliveButton.pack()
clearLogEntryButton = Button(tab1, text="Clear Log", command=clearLog)
clearLogEntryButton.pack()
pkReply = Text(tab1)
pkReply.pack()
##############################################################################################################################
listInstrumentsButton = Button(tab2, text="List Instruments", command=listInstruments)
listInstrumentsButton.pack()
instrumentLabel = Label(tab2, text="Enter The Instruments VISA Address")
instrumentLabel.pack()
instrumentEntry = ttk.Entry(tab2)
instrumentEntry.pack()
instrumentEntry.insert(0, "USB0::0x0957::0x0C07::MY54005119::INSTR")
setVoltageLabel = Label(tab2, text="Enter The Voltage")
setVoltageLabel.pack()
setVoltageEntry = ttk.Entry(tab2)
setVoltageEntry.pack()
setCurrentLabel = Label(tab2, text="Enter The Current")
setCurrentLabel.pack()
setCurrentEntry = ttk.Entry(tab2)
setCurrentEntry.pack()
setChannelLabel = Label(tab2, text="Enter The Channel")
setChannelLabel.pack()
setChannelEntry = ttk.Entry(tab2)
setChannelEntry.pack()
setChannelEntry.insert(0, "2")
readVoltageButton = Button(tab2, text="Read Voltage", command=readVoltage)
readVoltageButton.pack()
readCurrentButton = Button(tab2, text="Read Current", command=readCurrent)
readCurrentButton.pack()
setVoltageButton = Button(tab2, text="Set Voltage", command=setVoltage)
setVoltageButton.pack()
setCurrentButton = Button(tab2, text="Set Current", command=setCurrent)
setCurrentButton.pack()
outputOnRadioButton = ttk.Radiobutton(tab2, variable=output, text="On", value="OUTPut ON", command=toggleChannelOutput)
outputOnRadioButton.pack()
relayOffRadioButton = ttk.Radiobutton(tab2, variable=output, text="Off", value="OUTPut OFF", command=toggleChannelOutput)
relayOffRadioButton.pack()
clearLogButton = Button(tab2, text="Clear Log", command=clearPyvisaLog)
clearLogButton.pack()
visaReplyText = Text(tab2)
visaReplyText.pack()
##############################################################################################################################
relayIPLabel = ttk.Label(tab3, text="Relay Board IP")
relayIPLabel.pack()
relayIPEntry = ttk.Entry(tab3)
relayIPEntry.insert(0, "192.168.1.199")
relayIPEntry.pack()
relayNumberLabel = ttk.Label(tab3, text="Relay Number")
relayNumberLabel.pack()
relayNumberEntry = ttk.Entry(tab3)
relayNumberEntry.insert(0, "2")
relayNumberEntry.pack()
relayOnRadioButton = ttk.Radiobutton(tab3, variable=on_off, text="On", value="A") # A for active (turn on)
relayOnRadioButton.pack()
relayOffRadioButton = ttk.Radiobutton(tab3, variable=on_off, text="Off", value="I") # I for inactive (turn off)
relayOffRadioButton.pack()
sendRelayCommandButton = Button(tab3, text="Send Command", command=sendRelayCommand)
sendRelayCommandButton.pack()
cycleRelayButton = Button(tab3, text="Cycle Relay 10s", command=cycleRelay)
cycleRelayButton.pack()
##############################################################################################################################
root.mainloop()
现在我读到多线程将是一个合适的解决方案,但我不确定如何以最佳方式实现它。
我试图从函数“isDutAlive()”创建一个线程,并在按下触发该线程启动的按钮后启动它。但是当我想再次按下按钮时(为了再次执行函数和测试),它告诉我,一个线程只能启动一次。
有没有办法在执行函数后“杀死”线程?有什么建议可以在执行此功能期间使 GUI 响应吗?
我感谢任何帮助和提示。感谢和问候
我认为您的工作进展顺利。多线程确实是解决这个问题的一种方式。你实际上是在尝试做一些异步的事情。
最快的方法可能是像您提到的那样加入讨论帖。 this page on realpython.com. Specifically on the topic of barriers or deadlocks 是一个很好的资源,我认为它可以回答您的问题。
或者,如果您计算出这种情况发生得更多,并且结果可以在 X 时间后显示,而用户仍然没有积极等待,则可以使用某种带有代理的异步系统。我在类似情况下使用的是Celery。我将它与易于使用的 RabbitMQ 实例相结合。
使用此设置,您的用户将在预定义的队列中创建“todo”任务,然后这些任务将被处理。输出可以存储在您的应用程序可以订阅的结果队列中。使用此解决方案有一些优点和缺点,在您的情况下可能会或可能不会破坏。
优点:
- 真正的异步,所以只要您的应用程序可以到达 RMQ 服务器,它对您的系统的影响应该几乎为 0,并且 GUI 将始终保持“运行”状态。
- 无论用户按下多少次按钮,都无需等待结果。
缺点:
- 每个项目将单独执行。将 5 个项目放入队列意味着相同的操作将被执行 5 次。
- 需要额外的软件(RMQ 或其他代理)以及额外的库 (celery)
- 用户将主动获得他们行为的结果。这可能是一个障碍。
希望我已经为您提供了一些可能的解决方案?
我目前正在开发一个程序,该程序使用 Python 和 Tkinter 构建,用于与我们实验室中的不同仪器交互并测试一些电子设备。我有一个函数 (isDutAlive()),它可能需要很长时间(最多 10-15 分钟)才能完成。 现在,Tkinter GUI 在此期间没有响应,对用户来说,程序似乎崩溃了。也没有其他操作是可能的。 这是代码:
import os
import sys
import requests
import pyvisa
import time
from datetime import datetime
import threading
from jsonrpcclient import request
from tkinter import *
from tkinter import ttk
print("-----------------------------------------")
print(" Q-Center V0.1 ")
print("-----------------------------------------")
port = ":8080"
rm = pyvisa.ResourceManager()
def listArticles():
#print("PK IP Address: ")
#pkIpAddress = input()
response = request("http://" + ipEntry.get() + port, "temperature_test_list_articles")
print(response.text)
print(response.data.result)
answer = response.data.result
pkReply.insert(END, answer)
pkReply.insert(END, '\n\n')
def setArticle():
response = request("http://" + ipEntry.get() + port, "temperature_test_set_article", articleEntry.get())
print(response.text)
print(response.data.result)
answer = response.text
pkReply.insert(END, answer)
pkReply.insert(END, '\n\n')
def unsetArticle():
response = request("http://" + ipEntry.get() + port, "temperature_test_unset_article")
print(response.text)
print(response.data.result)
answer = response.text
pkReply.insert(END, answer)
pkReply.insert(END, '\n\n')
def clearLog():
pkReply.delete('1.0', END)
def listInstruments():
instruments = rm.list_resources()
print(instruments)
print('\n\n')
visaReplyText.insert(END, instruments)
visaReplyText.insert(END, '\n\n')
return 0
def isDutAlive():
timestamp = time.strftime("%Y_%m_%d-%H_%M_%S.txt")
logFile = open(timestamp, "a")
logFile.write("Timestamp DUT Result Voltage Current\n\n")
for x in range (int(firstSocketEntry.get()), int(lastSocketEntry.get())+1):
print(x)
time.sleep(1)
response = request("http://" + ipEntry.get() + port, "temperature_test_is_dut_alive", x)
time.sleep(1)
print(response.text)
print(response.data.result)
answer = response.data.result
pkReply.insert(END, answer)
pkReply.insert(END, '\n\n')
logFile.write(time.strftime("%Y_%m_%d-%H_%M_%S\t\t") + str(x) + "\t\t" + str(answer) + "\t\t" + str(readVoltage()) + "\t\t" + str(readCurrent()) + "\n")
print("TEST")
def clearPyvisaLog():
visaReplyText.delete('1.0', END)
return 0
def readVoltage():
choosenInstrument = rm.open_resource(instrumentEntry.get())
voltageMeasurement = choosenInstrument.query_ascii_values("MEAS:VOLT? (@" + setChannelEntry.get() + ")")
visaReplyText.insert(END, voltageMeasurement)
visaReplyText.insert(END, " V")
visaReplyText.insert(END, "\n\n")
return voltageMeasurement
def readCurrent():
choosenInstrument = rm.open_resource(instrumentEntry.get())
currentMeasurement = choosenInstrument.query_ascii_values("MEAS:CURR? (@" + setChannelEntry.get() + ")")
visaReplyText.insert(END, currentMeasurement)
visaReplyText.insert(END, " A")
visaReplyText.insert(END, "\n\n")
return currentMeasurement
def setVoltage():
choosenInstrument = rm.open_resource(instrumentEntry.get())
print(choosenInstrument.query('*IDN?'))
print(choosenInstrument.write("VOLT:LEV " + setVoltageEntry.get() + ", (@" + setChannelEntry.get() + ")"))
return 0
def setCurrent():
choosenInstrument = rm.open_resource(instrumentEntry.get())
print(choosenInstrument.query('*IDN?'))
print(choosenInstrument.write("CURR:LEV " + setCurrentEntry.get() + ", (@" + setChannelEntry.get() + ")"))
return 0
def toggleChannelOutput():
choosenInstrument = rm.open_resource(instrumentEntry.get())
#choosenInstrument.write("OUTPut OFF, (@2)")
choosenInstrument.write(output.get() + ", (@" + setChannelEntry.get() + ")")
return 0
def sendRelayCommand():
send = requests.get('http://' + relayIPEntry.get() + '/io.cgi?DO' + on_off.get() + relayNumberEntry.get())
print(on_off.get())
def cycleRelay():
send = requests.get('http://' + relayIPEntry.get() + '/io.cgi?DOI' + relayNumberEntry.get() + '=100')
root = Tk()
#root.geometry("800x400")
root.title("Q-Center")
root.iconbitmap("./icons/icon.ico")
root.call('wm', 'iconphoto', root._w, PhotoImage(file='./icons/icon.png'))
# root.iconphoto(False, PhotoImage(file='./icons/icon.png'))
on_off = StringVar()
output = StringVar()
##############################################################################################################################
tabControl = ttk.Notebook(root)
tab1 = ttk.Frame(tabControl)
tab2 = ttk.Frame(tabControl)
tab3 = ttk.Frame(tabControl)
tabControl.add(tab1, text="PK Control")
tabControl.add(tab2, text="Keysight Control")
tabControl.add(tab3, text="Relay Board Control")
tabControl.pack(expand=1, fill="both")
##############################################################################################################################
ipEntry = ttk.Entry(tab1)
ipEntry.pack()
ipEntry.insert(0, "192.168.1.73")
listArticlesButton = Button(tab1, text="List Articles", command=listArticles)
listArticlesButton.pack()
articleEntry = ttk.Entry(tab1)
articleEntry.pack()
setArticleButton = Button(tab1, text="Set Article", command=setArticle)
setArticleButton.pack()
unsetArticleButton = Button(tab1, text="Unset Article", command=unsetArticle)
unsetArticleButton.pack()
firstSocketTestLabel = Label(tab1, text="Enter First Socket To Test")
firstSocketTestLabel.pack()
firstSocketEntry = ttk.Entry(tab1)
firstSocketEntry.pack()
lastSocketTestLabel = Label(tab1, text="Enter Last Socket To Test")
lastSocketTestLabel.pack()
lastSocketEntry = ttk.Entry(tab1)
lastSocketEntry.pack()
isDutAliveButton = Button(tab1, text="Is DUT Alive", command=isDutAlive)
isDutAliveButton.pack()
clearLogEntryButton = Button(tab1, text="Clear Log", command=clearLog)
clearLogEntryButton.pack()
pkReply = Text(tab1)
pkReply.pack()
##############################################################################################################################
listInstrumentsButton = Button(tab2, text="List Instruments", command=listInstruments)
listInstrumentsButton.pack()
instrumentLabel = Label(tab2, text="Enter The Instruments VISA Address")
instrumentLabel.pack()
instrumentEntry = ttk.Entry(tab2)
instrumentEntry.pack()
instrumentEntry.insert(0, "USB0::0x0957::0x0C07::MY54005119::INSTR")
setVoltageLabel = Label(tab2, text="Enter The Voltage")
setVoltageLabel.pack()
setVoltageEntry = ttk.Entry(tab2)
setVoltageEntry.pack()
setCurrentLabel = Label(tab2, text="Enter The Current")
setCurrentLabel.pack()
setCurrentEntry = ttk.Entry(tab2)
setCurrentEntry.pack()
setChannelLabel = Label(tab2, text="Enter The Channel")
setChannelLabel.pack()
setChannelEntry = ttk.Entry(tab2)
setChannelEntry.pack()
setChannelEntry.insert(0, "2")
readVoltageButton = Button(tab2, text="Read Voltage", command=readVoltage)
readVoltageButton.pack()
readCurrentButton = Button(tab2, text="Read Current", command=readCurrent)
readCurrentButton.pack()
setVoltageButton = Button(tab2, text="Set Voltage", command=setVoltage)
setVoltageButton.pack()
setCurrentButton = Button(tab2, text="Set Current", command=setCurrent)
setCurrentButton.pack()
outputOnRadioButton = ttk.Radiobutton(tab2, variable=output, text="On", value="OUTPut ON", command=toggleChannelOutput)
outputOnRadioButton.pack()
relayOffRadioButton = ttk.Radiobutton(tab2, variable=output, text="Off", value="OUTPut OFF", command=toggleChannelOutput)
relayOffRadioButton.pack()
clearLogButton = Button(tab2, text="Clear Log", command=clearPyvisaLog)
clearLogButton.pack()
visaReplyText = Text(tab2)
visaReplyText.pack()
##############################################################################################################################
relayIPLabel = ttk.Label(tab3, text="Relay Board IP")
relayIPLabel.pack()
relayIPEntry = ttk.Entry(tab3)
relayIPEntry.insert(0, "192.168.1.199")
relayIPEntry.pack()
relayNumberLabel = ttk.Label(tab3, text="Relay Number")
relayNumberLabel.pack()
relayNumberEntry = ttk.Entry(tab3)
relayNumberEntry.insert(0, "2")
relayNumberEntry.pack()
relayOnRadioButton = ttk.Radiobutton(tab3, variable=on_off, text="On", value="A") # A for active (turn on)
relayOnRadioButton.pack()
relayOffRadioButton = ttk.Radiobutton(tab3, variable=on_off, text="Off", value="I") # I for inactive (turn off)
relayOffRadioButton.pack()
sendRelayCommandButton = Button(tab3, text="Send Command", command=sendRelayCommand)
sendRelayCommandButton.pack()
cycleRelayButton = Button(tab3, text="Cycle Relay 10s", command=cycleRelay)
cycleRelayButton.pack()
##############################################################################################################################
root.mainloop()
现在我读到多线程将是一个合适的解决方案,但我不确定如何以最佳方式实现它。 我试图从函数“isDutAlive()”创建一个线程,并在按下触发该线程启动的按钮后启动它。但是当我想再次按下按钮时(为了再次执行函数和测试),它告诉我,一个线程只能启动一次。 有没有办法在执行函数后“杀死”线程?有什么建议可以在执行此功能期间使 GUI 响应吗? 我感谢任何帮助和提示。感谢和问候
我认为您的工作进展顺利。多线程确实是解决这个问题的一种方式。你实际上是在尝试做一些异步的事情。
最快的方法可能是像您提到的那样加入讨论帖。 this page on realpython.com. Specifically on the topic of barriers or deadlocks 是一个很好的资源,我认为它可以回答您的问题。
或者,如果您计算出这种情况发生得更多,并且结果可以在 X 时间后显示,而用户仍然没有积极等待,则可以使用某种带有代理的异步系统。我在类似情况下使用的是Celery。我将它与易于使用的 RabbitMQ 实例相结合。
使用此设置,您的用户将在预定义的队列中创建“todo”任务,然后这些任务将被处理。输出可以存储在您的应用程序可以订阅的结果队列中。使用此解决方案有一些优点和缺点,在您的情况下可能会或可能不会破坏。
优点:
- 真正的异步,所以只要您的应用程序可以到达 RMQ 服务器,它对您的系统的影响应该几乎为 0,并且 GUI 将始终保持“运行”状态。
- 无论用户按下多少次按钮,都无需等待结果。
缺点:
- 每个项目将单独执行。将 5 个项目放入队列意味着相同的操作将被执行 5 次。
- 需要额外的软件(RMQ 或其他代理)以及额外的库 (celery)
- 用户将主动获得他们行为的结果。这可能是一个障碍。
希望我已经为您提供了一些可能的解决方案?