我正在编写在新进程中执行子程序的程序,并在单独的线程中读取这些程序的标准输出并将其写入StyledTextCtrl
控件。我遇到了停止执行线程的问题。我有以下代码:
import subprocess
from threading import Thread, Lock
import wx
import wx.stc
import logging
logging.basicConfig(level=logging.DEBUG,
format='[%(levelname)s] (%(threadName)-10s) %(module)10s:%(funcName)-15s %(message)s',
)
class ReadThread(Thread):
def __init__(self, subp, fd, append_text_callback):
Thread.__init__(self)
self.killed = False
self.subp = subp
self.fd = fd
self.append_text_callback = append_text_callback
def run(self):
chunk = None
while chunk != '' and not self.killed:
self.subp.poll()
chunk = self.fd.readline()
if chunk:
self.append_text_callback('%d: ' % self.subp.pid + chunk)
if chunk == "":
break
logging.debug('END')
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Example1")
panel = wx.Panel(self, wx.ID_ANY)
self.log = wx.stc.StyledTextCtrl(panel, wx.ID_ANY, size=(300,100), style=wx.TE_MULTILINE|wx.TE_READONLY|wx.HSCROLL)
run_button = wx.Button(panel, wx.ID_ANY, 'Run')
self.Bind(wx.EVT_BUTTON, self.onRun, run_button)
self.Bind(wx.EVT_CLOSE, self.OnCloseFrame)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.log, 1, wx.ALL|wx.EXPAND, 5)
sizer.Add(run_button, 0, wx.ALL|wx.CENTER, 5)
panel.SetSizer(sizer)
self.read_thread_1 = None
self.read_thread_2 = None
self.log_lock = Lock()
def OnCloseFrame(self, event):
logging.debug('CLOSE')
if self.read_thread_1:
self.read_thread_1.killed = True
if self.read_thread_2:
self.read_thread_2.killed = True
if self.read_thread_1:
self.read_thread_1.join(timeout=1)
logging.debug('after 1 join')
if self.read_thread_2:
self.read_thread_2.join(timeout=1)
logging.debug('after 2 join')
self.read_thread_1 = None
self.read_thread_2 = None
event.Skip()
def onRun(self, event):
cmd1 = "E:/threading_block/stdout_emulator.exe 50 500"
subp1 = subprocess.Popen(cmd1, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
self.read_thread_1 = ReadThread(subp1, subp1.stdout, self.AppendText)
self.read_thread_1.start()
cmd2 = "E:/threading_block/stdout_emulator.exe 21 400"
sp2 = subprocess.Popen(cmd2, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
self.read_thread_2 = ReadThread(sp2, sp2.stdout, self.AppendText)
self.read_thread_2.start()
logging.debug('END')
def AppendText(self, text):
self.log_lock.acquire()
logging.debug('%s' % text)
self.log.AddText(text)
self.log_lock.release()
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = MyForm().Show()
app.MainLoop()
我在OnCloseFrame
方法中停止了线程。我在killed
值中设置了True
属性,因此它导致在while
类的run
方法中完成ReadThread
循环的执行。
在执行线程期间关闭此应用程序(在Windows下)时没有问题。但是,如果我从timeout=1
和self.read_thread_1.join(timeout=1)
删除self.read_thread_2.join(timeout=1)
,则会阻止该计划。
该程序在self.read_thread_1.join()
方法中OnCloseFrame
之前停滞不前。
这种阻止的问题是什么?为什么我应该timeout
使用join
?
std_emulator.exe的C代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <time.h>
int main(int argc, char *argv[])
{
int s;
int n;
if (argc > 1)
{
s = atoi(argv[1]);
n = atoi(argv[2]);
}
else
{
s = 1;
n = 50;
}
int i;
for (i = s; i < n; ++i)
{
printf("This is %d line\n", i);
fflush(stdout);
if (i % 2 == 0)
{
srand(time(NULL));
int r = rand() % 7;
printf("sleep_type_2 with %d\n", r);
Sleep(30 * r);
fflush(stdout);
}
if (i % 3 == 0)
{
int r = rand() % 7;
printf("sleep_type_3 with %d\n", r);
Sleep(40 * r);
fflush(stdout);
}
if (i % 5 == 0)
{
int r = rand() % 9;
printf("sleep_type_5 with %d\n", r);
Sleep(50);
fflush(stdout);
}
}
}
答案 0 :(得分:1)
在你的情况下,当你使用join(timeout=1)
时,你实际上并没有杀死线程。相反,join
操作正在等待一秒钟以查看线程是否退出,而当它没有退出时,它就会放弃。 join
无法正常工作,因为您的帖子很可能会阻止此行:
chunk = self.fd.readline()
所以它永远不会循环,看看killed
是否已设置为True
。我认为干净地关闭线程的最佳方法是终止在ReadThread
中OnCloseFrame
中运行的子进程:
if self.read_thread_1:
self.read_thread_1.subp.terminate()
self.read_thread_1.join()
if self.read_thread_2:
self.read_thread_2.subp.terminate()
self.read_thread_2.join()
终止子进程将取消阻止readline
调用,并允许您干净地终止该线程。