通过在Popen进程中发送.communicate请求来冻结tkinter窗口

huangapple go评论135阅读模式
英文:

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([&#39;cmd&#39;], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
        self.process.stdin.write(b&#39;ping -t google.com\n&#39;)
        self.process.stdin.flush()

        while not self.stop_event.is_set():
            line = self.process.stdout.readline().decode(&#39;cp866&#39;)
            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&#39;\x03&#39;)
            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, &#39;\nProcess terminated.\n&#39;)

    root.bind(&#39;&lt;Control-c&gt;&#39;, handle_ctrl_c)

root = tk.Tk()

text = tk.Text(root)
text.pack()

button = tk.Button(root, text=&#39;Ping&#39;, 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&#39;\x03&#39;)

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:

  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)

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([&#39;cmd&#39;], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, creationflags=subprocess.CREATE_NEW_PROCESS_GROUP)
self.process.stdin.write(b&#39;ping -t google.com\n&#39;)
self.process.stdin.flush()
while not self.stop_event.is_set():
line = self.process.stdout.readline().decode(&#39;cp866&#39;)
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(&#39;cp866&#39;))
for i in range(4):
self.text_widget.insert(tk.END, self.process.stdout.readline().decode(&#39;cp866&#39;))
self.text_widget.see(tk.END)
i += 1
self.process.stdout.close()
self.process.kill()
self.text_widget.insert(tk.END, &#39;\nProcess terminated.\n&#39;)
root.unbind(&#39;&lt;Control-c&gt;&#39;)
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(&#39;&lt;Control-c&gt;&#39;, handle_ctrl_c)
root = tk.Tk()
text = tk.Text(root)
text.pack()
button = tk.Button(root, text=&#39;Ping&#39;, command=ping)
button.pack()
root.mainloop()

huangapple
  • 本文由 发表于 2023年3月7日 06:32:40
  • 转载请务必保留本文链接:https://go.coder-hub.com/75656453.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定