Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/285.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/multithreading/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
无法理解为什么在Windows下的(wx)Python中,停止线程通常会挂起该线程的剩余执行代码_Python_Multithreading_Wxpython - Fatal编程技术网

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

无法理解为什么在Windows下的(wx)Python中,停止线程通常会挂起该线程的剩余执行代码,python,multithreading,wxpython,Python,Multithreading,Wxpython,我有一个wxPython代码,在Windows 10 64位/Python 3.9.0 64位/wx'4.1.1 msw(phoenix)wxWidgets 3.1.5'下运行,它在外部文件中启动一个线程(如果这对我的问题有任何影响的话)。真实代码启动telnet会话,但为了简单(和理解),我创建了一个单独的工作测试程序,如下所示,它遵循与真实代码相同的逻辑,只是它删除了telnet部分 该程序有一个“连接”工具栏按钮,用于启动状态栏上显示报告消息的线程;还有一个“断开”按钮,用于正常停止线程(

我有一个wxPython代码,在Windows 10 64位/Python 3.9.0 64位/wx'4.1.1 msw(phoenix)wxWidgets 3.1.5'下运行,它在外部文件中启动一个线程(如果这对我的问题有任何影响的话)。真实代码启动telnet会话,但为了简单(和理解),我创建了一个单独的工作测试程序,如下所示,它遵循与真实代码相同的逻辑,只是它删除了telnet部分

该程序有一个“连接”工具栏按钮,用于启动状态栏上显示报告消息的线程;还有一个“断开”按钮,用于正常停止线程(至少我是这么想的),状态栏上显示报告消息

单击工具栏上的“连接”按钮(即“+”按钮),线程开始正常运行

我的问题是:只要我单击工具栏上的“断开连接”按钮(即“-”按钮),程序就会挂起,这可能是因为执行了无休止的线程。这就像
stopped
函数冻结一切,而不仅仅是让
while…
循环离开并简单地沿着它的路径离开

通过在非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错误,很可能是由于我简化的wx测试代码的一些缺陷,但我只是暂时忽略了这一点)

也许这不是Windows特有的问题,也许我的程序的逻辑有一些缺陷

我错过了什么

注意:为了简化此测试,程序窗口的close按钮不会在程序退出之前尝试结束线程(如果已启动),并且“Disconnect”按钮事件不会检查是否确实有东西要断开

稍后编辑:我在相同的窗口下测试了它,也使用了Python 3.9.1 32位/wx'4.1.1 msw(phoenix)wxWidgets 3.1.5'(这一个是使用32位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)
和外部文件上的代码(实际线程):

测试窗口(Windows下)如下所示(单击工具栏上的“连接”按钮后显示):


首先必须承认这是不直观的。试图省略
self.ctrl\u 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")
我想您不相信线程会自动退出,因为您最终会杀死所有东西:)

简而言之,在wxPython代码中,必须永远不要有阻塞的代码,因为这将阻止任何进一步的代码被处理。输入线程


在您的情况下,wx事件循环进入您的方法
ctrl\u disconnect
join()
表示它正在等待线程停止。但是,线程中的代码试图让wxPython返回ecececute代码(
self.wx\u ui.frame\u statusbar.SetStatusText
),这无法继续,因为wxPython循环仍在等待
join()
完成。

答案在wxPython代码中的语句中,您永远不能有阻塞的代码,因为它将阻止任何进一步的代码被处理。事实上,问题在于
.join()
事实(而不是方法),因为我还尝试了其他一些不同的等待线程关闭的方法,这些方法实际上以相同的方式锁定了程序。事实证明,移动特定于UI的状态栏更新并不重要(我以前尝试过完全删除它),而且也不是一个解决方案,因为在我的实际代码中,线程必须在那里写入其他几条消息。总之,谢谢:)(继续上面的评论,因为它太长了)但是,我不明白为什么这个测试在Raspberry Pi(即Linux)上“按原样”工作;这完全把我弄糊涂了。别担心:在Debian 64位Buster(
apt install python3-wxgtk4.0
)上尝试了问题中发布的代码。它会在按下connect时崩溃。在线程对wxPython的第一次调用中也必须移出。我认为raspbian wxPython已经足够多了,它可以让您避免线程错误处理:)另一个注意事项:wxPython不是线程安全的。因此,建议从非wx线程使用wx.CallAfter(线程安全)。如下所示:
wx.CallAfter(self.wx\u ui.frame\u statusbar.SetStatusText,“断开”)
。您可能已经猜到了:这将在Debian上起作用,但在Windows上不起作用:/I按照上面的建议测试了
wx.CallAfter(self.etc.)
,它在Windows和Raspberry Pi OS上都能很好地工作:)这与公认答案的主要解决方案(即避免/删除任何等待/阻塞代码)相结合,产生了一个更强大的工作程序。谢谢:)
    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)