python - 无法理解为什么正常停止线程会在Windows下的(wx)Python中挂起该线程的其余执行代码

标签 python multithreading wxpython

我有一个在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下)如下所示(单击工具栏上的“连接”按钮后立即显示在此处):
wx interface in 'connected' status

最佳答案

首先必须承认这是不直观的。试图省略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_disconnectjoin()表示它正在等待线程停止。但是,线程中的代码尝试使wxPython执行代码(self.wx_ui.frame_statusbar.SetStatusText),该操作无法继续,因为wxPython循环仍在等待join()完成。

关于python - 无法理解为什么正常停止线程会在Windows下的(wx)Python中挂起该线程的其余执行代码,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65971103/

相关文章:

python - 我应该将 C 库与我的 Python 应用程序捆绑在一起吗?

python - Docker Compose 和 Postgres 连接被拒绝

python - wxPython:全局热键循环切换

python - 无监督人口分类

python - 如何生成具有足够间距以容纳大量数字的曲线图

java - 无法附加到 HDFS

c# - 可以将其当前计数减少 N(N>=1)的信号量?

multithreading - 多线程运行Stanford CoreNLP服务器

python - 如何获得 wx.ListCtrl 的宽度及其列名?

python - 如何使用 wxPython 将 Windows UAC Shield 覆盖在按钮上?