我有一个在Windows 10 64位/Python 3.9.0 64位/wx'4.1.1 msw(phoenix)wxWidgets 3.1.5'下运行的wxPython代码,它将在外部文件中启动线程(如果这对我来说很重要问题)。真实的代码启动了telnet session ,但是为了简单起见(和理解),我创建了一个单独的工作测试程序,如下所示,该程序遵循与真实代码相同的逻辑,只是删除了telnet部分。
该程序有一个“连接”工具栏按钮和一个“断开连接”按钮,该线程以状态栏上的报告消息启动线程,而“断开连接”按钮应再次以状态栏上的报告消息正常(至少是我的意图)停止线程。
通过单击工具栏上的“连接”按钮(即“+”按钮),该线程将开始运行。
我的问题:只要我单击工具栏上的“断开连接”按钮(即“-”按钮),程序就会挂起,这可能是因为线程执行无休止。就像stopped
函数冻结了所有内容一样,不仅仅是让while ...
循环离开而只是跟随它的出路。
通过用类似while not self.stopped():
的方式更改线程的循环while True
语句,然后通过几秒钟的超时触发break
(因此,无需进一步触摸“断开连接”按钮),线程就可以在超时后正常退出,就好像什么都没发生一样-因此问题与实际的线程停止机制一起使用。
但是,在Raspberry Pi OS(Buster)/Python 3.7.3/wx'4.0.7 post2 gtk3(phoenix)wxWidgets 3.0.5'下运行相同的测试程序时,挂起不再发生(我到那里来了一些Gtk警告&随机Gdk-ERROR,最有可能是由于我简化的wx测试代码上的某些缺陷,但我现在暂时忽略了这一点)。
也许这不是Windows特有的问题,也许我程序的逻辑有一些缺陷。
我在这里想念什么?
注意:为简化测试,程序窗口的关闭按钮不会在程序退出之前尝试结束线程(如果已启动),并且“Disconnect”按钮事件不会检查是否确实有要断开的东西。
稍后编辑:我在相同的Windows上,也使用Python 3.9.1 32bit/wx'4.1.1 msw(phoenix)wxWidgets 3.1.5'(使用32bit Python安装的pip安装)进行了测试。 ,程序将以相同的方式挂起。
主要代码:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import test_wx_th_ext_file as TestWxThExtFile
import wx
import threading
import time
import os
import sys
##
class MyFrame(wx.Frame):
def __init__(self, *args, **kwds):
kwds["style"] = kwds.get("style", 0) | wx.DEFAULT_FRAME_STYLE
wx.Frame.__init__(self, *args, **kwds)
self.SetSize((400, 300))
self.SetTitle("test wx th")
self.frame_statusbar = self.CreateStatusBar(1)
self.frame_statusbar.SetStatusWidths([-1])
# Tool Bar
self.frame_toolbar = wx.ToolBar(self, -1)
tool = self.frame_toolbar.AddTool(wx.ID_ANY, "Connect", wx.ArtProvider.GetBitmap(wx.ART_PLUS, wx.ART_TOOLBAR, (24, 24)), wx.NullBitmap, wx.ITEM_NORMAL, "Connect", "")
self.Bind(wx.EVT_TOOL, self.ctrl_connect, id=tool.GetId())
tool = self.frame_toolbar.AddTool(wx.ID_ANY, "Disconnect", wx.ArtProvider.GetBitmap(wx.ART_MINUS, wx.ART_TOOLBAR, (24, 24)), wx.NullBitmap, wx.ITEM_NORMAL, "Disconnect", "")
self.Bind(wx.EVT_TOOL, self.ctrl_disconnect, id=tool.GetId())
self.SetToolBar(self.frame_toolbar)
self.frame_toolbar.Realize()
# Tool Bar end
self.panel_1 = wx.Panel(self, wx.ID_ANY)
self.Layout()
def ctrl_connect(self, event):
self.ctrl_thread = TestWxThExtFile.ControllerTn(self)
self.ctrl_thread.daemon = True
self.ctrl_thread.start()
def ctrl_disconnect(self, event):
self.ctrl_thread.stopit()
self.ctrl_thread.join()
##
class MyApp(wx.App):
def OnInit(self):
self.frame = MyFrame(None, wx.ID_ANY, "")
self.SetTopWindow(self.frame)
self.frame.Show()
return True
##
if __name__ == "__main__":
app = MyApp(0)
app.MainLoop()
try:
sys.exit(0)
except SystemExit:
os._exit(0)
和外部文件上的代码(实际线程):#!/usr/bin/env python
# -*- coding: utf-8 -*-
import threading
import time
##
class ControllerTh(threading.Thread):
def __init__(self):
super().__init__()
self._stopper = threading.Event()
def stopit(self):
self._stopper.set()
def stopped(self):
return self._stopper.is_set()
##
class ControllerTn(ControllerTh):
def __init__(self, wx_ui):
ControllerTh.__init__(self)
self.wx_ui = wx_ui
def run(self):
print ("th_enter")
self.wx_ui.frame_statusbar.SetStatusText("Connected")
while not self.stopped():
print ("th_loop")
time.sleep(1)
self.wx_ui.frame_statusbar.SetStatusText("Disconnected")
print ("th_exit")
测试窗口(在Windows下)如下所示(单击工具栏上的“连接”按钮后立即显示在此处):最佳答案
首先必须承认这是不直观的。试图省略self.ctrl_thread.join()
。这是造成问题的真正原因,您需要考虑另一种方法来安全地停止线程。
要使其运行,请将特定于UI的状态栏更新从线程移回框架。问题解决了。在线程中:
while not self.stopped():
print ("th_loop")
time.sleep(1)
# move this to the frame
# self.wx_ui.frame_statusbar.SetStatusText("Disconnected")
print ("th_exit")
在框架中: def ctrl_disconnect(self, event):
self.ctrl_thread.stopit()
self.ctrl_thread.join()
# thread has joined, signal end of thread
self.frame_statusbar.SetStatusText("Disconnected")
我想您不相信线程本身会退出,因为无论如何您最终都将杀死所有内容:)# hooray, all threads will be dead :)
try:
sys.exit(0)
except SystemExit:
os._exit(0)
简而言之,在wxPython代码中,必须永远不会阻塞代码,因为这将阻止任何进一步的代码被处理。输入线程。就您而言,wx事件循环输入您的方法
ctrl_disconnect
。 join()
表示它正在等待线程停止。但是,线程中的代码尝试使wxPython执行代码(self.wx_ui.frame_statusbar.SetStatusText
),该操作无法继续,因为wxPython循环仍在等待join()
完成。
关于python - 无法理解为什么正常停止线程会在Windows下的(wx)Python中挂起该线程的其余执行代码,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65971103/