英文:
Freezing tkinter window via sending .communicate request in Popen process
问题
我有这段代码,这是我想象力和ChatGPT帮助的产物:
import subprocess
import threading
import tkinter as tk
class PingThread(threading.Thread):
def __init__(self, text_widget):
super().__init__()
self.text_widget = text_widget
self.process = None
self.stop_event = threading.Event()
def run(self):
self.process = subprocess.Popen(['cmd'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
self.process.stdin.write(b'ping -t google.com\n')
self.process.stdin.flush()
while not self.stop_event.is_set():
line = self.process.stdout.readline().decode('cp866')
if not line:
break
self.text_widget.insert(tk.END, line)
self.text_widget.see(tk.END)
def stop(self):
if self.process:
self.process.communicate(b'\x03')
self.process.wait()
self.stop_event.set()
def ping():
ping_thread = PingThread(text)
ping_thread.start()
def handle_ctrl_c(event):
ping_thread.stop()
text.insert(tk.END, '\nProcess terminated.\n')
root.bind('<Control-c>', handle_ctrl_c)
root = tk.Tk()
text = tk.Text(root)
text.pack()
button = tk.Button(root, text='Ping', command=ping)
button.pack()
root.mainloop()
我正在尝试在Tkinter中创建一个控制台模拟。我发送ping请求并监听控制台以获取其响应。一切都运行正常,除了Ctrl+C
命令,它应该结束ping
执行并从控制台返回统计信息。当我尝试发送self.process.communicate(b'\x03')
时,窗口会冻结。这是什么原因?据我了解,这一行应该发送Ctrl+C
到控制台,并且while循环
应该接收来自控制台的最后几行,包括ping的统计信息。
英文:
I have this code, product of my imagination and ChatGPT help:
import subprocess
import threading
import tkinter as tk
class PingThread(threading.Thread):
def __init__(self, text_widget):
super().__init__()
self.text_widget = text_widget
self.process = None
self.stop_event = threading.Event()
def run(self):
self.process = subprocess.Popen(['cmd'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
self.process.stdin.write(b'ping -t google.com\n')
self.process.stdin.flush()
while not self.stop_event.is_set():
line = self.process.stdout.readline().decode('cp866')
if not line:
break
self.text_widget.insert(tk.END, line)
self.text_widget.see(tk.END)
def stop(self):
if self.process:
self.process.communicate(b'\x03')
self.process.wait() #Window freezes on the .communicate line, so this and line below are not executing at all
self.stop_event.set()
def ping():
ping_thread = PingThread(text)
ping_thread.start()
def handle_ctrl_c(event):
ping_thread.stop()
text.insert(tk.END, '\nProcess terminated.\n')
root.bind('<Control-c>', handle_ctrl_c)
root = tk.Tk()
text = tk.Text(root)
text.pack()
button = tk.Button(root, text='Ping', command=ping)
button.pack()
root.mainloop()
I'm trying to create a console simulation in Tkinter. I'm sending ping request and listening console for it's response. All works well, except Ctrl+C
command, which should finishes ping
execution and response the statistics from console. Window just freezing, when i try to send self.process.communicate(b'\x03')
What causes that? As i understand, this line should send Ctrl+C
to the console and while loop
should receive last lines from the console, with ping's statistics?
答案1
得分: 1
以下是代码部分的翻译:
So now that I better understand your issue, here is another try. My answer is not working perfectly, because I use a workaround to get the last 4 lines after sending `CTRL_BREAK_EVENT`
然而,现在我更好地理解了您的问题,所以这里有另一次尝试。我的答案并不完美,因为我使用了一种解决方法来在发送 `CTRL_BREAK_EVENT` 后获取最后的 4 行。
However, for your question the 2 most important things:
1) Use `creationflags=subprocess.CREATE_NEW_PROCESS_GROUP` when you create your process. If I understand it correctly, this creates a different process for the terminal and the ping command, so you can still get the output from the terminal after passing the stop signal.
2) To terminate the `ping` command, send `signal.CTRL_BREAK_EVENT` on Windows. In general, these signals seem to be not consistent for operating systems. (see also the comment from @chris_se)
然而,对于您的问题,有两个最重要的事项:
1) 在创建进程时使用 `creationflags=subprocess.CREATE_NEW_PROCESS_GROUP`。如果我理解正确,这将为终端和 ping 命令创建不同的进程,因此在传递停止信号后,您仍然可以获取终端的输出。
2) 要终止 `ping` 命令,请在 Windows 上发送 `signal.CTRL_BREAK_EVENT`。一般来说,这些信号在不同操作系统上可能不一致。(也请参考 @chris_se 的评论)
The workaround I use is, to only read the next 4 lines of `STDOUT` after setting the stop event (exiting the while loop). I know that the statistics summary of the `ping` command will print this number of lines. Of course we could increase it to be sure to not miss anything.
我使用的解决方法是,在设置停止事件后(退出 while 循环)仅读取 `STDOUT` 的下一行 4 行。我知道 `ping` 命令的统计摘要会打印这些行数。当然,我们可以增加这个数字以确保不会错过任何内容。
Here is the code:
以下是代码部分的翻译:
```python
import subprocess
import threading
import tkinter as tk
import signal
import subprocess
import threading
import tkinter as tk
import signal
class PingThread(threading.Thread):
def __init__(self, text_widget):
super().__init__()
self.text_widget = text_widget
self.process = None
self.stop_event = threading.Event()
def run(self):
self.process = subprocess.Popen(['cmd'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, creationflags=subprocess.CREATE_NEW_PROCESS_GROUP)
self.process.stdin.write(b'ping -t google.com\n')
self.process.stdin.flush()
while not self.stop_event.is_set():
line = self.process.stdout.readline().decode('cp866')
if not line:
break
self.text_widget.insert(tk.END, line)
self.text_widget.see(tk.END)
self.text_widget.insert(tk.END, self.process.stdout.readline().decode('cp866'))
for i in range(4):
self.text_widget.insert(tk.END, self.process.stdout.readline().decode('cp866'))
self.text_widget.see(tk.END)
i += 1
self.process.stdout.close()
self.process.kill()
self.text_widget.insert(tk.END, '\nProcess terminated.')
root.unbind('<Control-c>')
def stop(self):
if self.process:
self.stop_event.set()
self.process.send_signal(signal.CTRL_BREAK_EVENT)
def ping():
ping_thread = PingThread(text)
ping_thread.start()
def handle_ctrl_c(event):
ping_thread.stop()
root.bind('<Control-c>', handle_ctrl_c)
root = tk.Tk()
text = tk.Text(root)
text.pack()
button = tk.Button(root, text='Ping', command=ping)
button.pack()
root.mainloop()
希望这些翻译对您有所帮助。如有任何疑问,请随时提出。
英文:
So now that I better understand your issue, here is another try. My answer is not working perfectly, because I use a workaround to get the last 4 lines after sending CTRL_BREAK_EVENT
However, for your question the 2 most important things:
- Use
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP
when you create your process. If I understand it correctly, this creates a different process for the terminal and the ping command, so you can still get the output from the terminal after passing the stop signal. - To terminate the
ping
command, sendsignal.CTRL_BREAK_EVENT
on Windows. In general, these signals seem to be not consistent for operating systems. (see also the comment from @chris_se)
The workaround I use is, to only read the next 4 lines of STDOUT
after setting the stop event (exiting the while loop). I know that the statistics summary of the ping
command will print this number of lines. Of course we could increase it to be sure to not miss anything.
Here is the code:
import subprocess
import threading
import tkinter as tk
import signal
class PingThread(threading.Thread):
def __init__(self, text_widget):
super().__init__()
self.text_widget = text_widget
self.process = None
self.stop_event = threading.Event()
def run(self):
self.process = subprocess.Popen(['cmd'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, creationflags=subprocess.CREATE_NEW_PROCESS_GROUP)
self.process.stdin.write(b'ping -t google.com\n')
self.process.stdin.flush()
while not self.stop_event.is_set():
line = self.process.stdout.readline().decode('cp866')
if not line:
break
self.text_widget.insert(tk.END, line)
self.text_widget.see(tk.END)
self.text_widget.insert(tk.END, self.process.stdout.readline().decode('cp866'))
for i in range(4):
self.text_widget.insert(tk.END, self.process.stdout.readline().decode('cp866'))
self.text_widget.see(tk.END)
i += 1
self.process.stdout.close()
self.process.kill()
self.text_widget.insert(tk.END, '\nProcess terminated.\n')
root.unbind('<Control-c>')
def stop(self):
if self.process:
self.stop_event.set()
self.process.send_signal(signal.CTRL_BREAK_EVENT)
def ping():
ping_thread = PingThread(text)
ping_thread.start()
def handle_ctrl_c(event):
ping_thread.stop()
root.bind('<Control-c>', handle_ctrl_c)
root = tk.Tk()
text = tk.Text(root)
text.pack()
button = tk.Button(root, text='Ping', command=ping)
button.pack()
root.mainloop()
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论