Kill program generated by subprocess with PyQt5 - python

This is a program created as an example for explanation.
main.py
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QGridLayout, QPushButton
from worker import Worker
class TestUI(QWidget):
def __init__(self):
super().__init__()
self.worker = Worker()
self.init_ui()
def init_ui(self):
run_btn = QPushButton("Run")
run_btn.clicked.connect(self.run)
kill_btn = QPushButton("Kill")
kill_btn.clicked.connect(self.kill)
layout = QGridLayout()
layout.addWidget(run_btn, 0, 0)
layout.addWidget(kill_btn, 0, 1)
self.setLayout(layout)
def run(self):
self.worker.run_command("calc.exe")
def kill(self):
self.worker.kill_command()
if __name__ == "__main__":
APP = QApplication(sys.argv)
ex = TestUI()
ex.show()
sys.exit(APP.exec_())
worker.py
import os
import signal
import subprocess
from PyQt5.QtCore import QObject
class Worker(QObject):
def __init__(self):
super().__init__()
def run_command(self, cmd):
self.proc = subprocess.Popen(cmd)
def kill_command(self):
# self.proc.kill() => Not working
# self.proc.terminate() => Not working
# os.kill(self.proc.pid, signal.SIGTERM) => Not working
I want to kill the program generated by subprocess when I click the kill button. In other words, I want to send a kill signal to the program like pressing CTRL+C.
And when the main PyQt5 program is terminated, I want the program generated by subprocess to terminate as well.
How can I do this?
Please help me with this problem.

Finally, I found the method.
I used a psutil module.
This is not a built-in module. So you need to install it first.
pip install psutil
And, now you can use this module like below.
def kill_command(self):
for proc in psutil.process_iter():
if "calc" in proc.name().lower():
os.kill(proc.pid, signal.SIGTERM)

Related

Periodic task in Qt without interrupting events

I have a graphic application in python using QWidget. The user can change elements by moving the mouse (using mouseMoveEvent), and the program should periodically (e.g., once per second) compute a function update_forces based on these elements.
Problem: the mouseMoveEvent doesn't trigger as often while update_forces is computed, so the program becomes periodically unresponsive. There are various tutorials online on how to remedy this using threads, but their solutions don't work for me, and I don't know why.
MWE
# Imports
import sys
import random
import time
from PyQt5.QtCore import QThread, pyqtSignal, QObject
from PyQt5.QtWidgets import QWidget, QApplication
# update_forces dummy
def update_forces():
for i in range(10000000):
x = i**2
# Worker object:
class Worker(QObject):
finished = pyqtSignal()
def run(self):
while(True):
update_forces()
time.sleep(1)
# and the Window class with a dummy mouseEvent that just prints a random number
class Window(QWidget):
def __init__(self):
super().__init__()
self.initUI()
self.setMouseTracking(True)
def initUI(self):
self.setGeometry(20, 20, 500, 500)
self.show()
self.thread = QThread()
self.worker = Worker()
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.run)
self.worker.finished.connect(self.thread.quit)
self.worker.finished.connect(self.worker.deleteLater)
self.thread.finished.connect(self.thread.deleteLater)
self.thread.start()
def mouseMoveEvent(self, e):
print(random.random())
# head procedure
if __name__ == '__main__':
app = QApplication(sys.argv)
w = Window()
w.show()
sys.exit(app.exec_())
When I run this (Windows 10) and move my cursor around on the screen, I can clearly observe two phases: it goes from printing numbers rapidly (while update_forces is not computed), to printing them much more slowly (while it is computed). It keeps alternating between both.
The solution is to use multiprocessing rather than threading.
from multiprocessing import Process
if __name__ == '__main__':
p = Process(target=updater, args=())
p.start()
def updater(queue,test):
while(True):
update_forces()
time.sleep(1)
The problem with this is that no data is shared between processes by default, but that's solvable.

threading queue blocks main pyqt5 GUI window

Although I read a ton of posts on the web, but couldn't find a solution for my self.
Basically, I am using pyqt5 for making a GUI application and am using threading for creating threads and lastly using queue I tried to send signals between main and child thread.
I have a qt5 progress bar in which it is value needs to updated by the child thread progress. When I try to use progressbar.setValue(x) I get below error:
QObject::setParent: Cannot set parent, new parent is in a different thread
The reason for above is that I cannot update the progress bar value from child thread, it needs to happen from main thread.
So I tried to use queue and send a message to the main thread to do it.
I tried below:
def buttonclicked():
global progressQueue
progressQueue = Queue()
thread = threading.Thread(target=childfunc, daemon=True, name="nemo", args=())
thread.start()
progressQueue.join()
while True:
if not progressQueue.empty():
msg = progressQueue.get()
pellow.setValue(msg)
return 0
def childfunc():
for i in range(1,100):
progressQueue.put(i)
Here the problem is that, the while loop freezes the mainwindow/gui app from interaction. Can anyone suggest, what do I need to do here?
A complete working example:
import queue
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from threading import Thread
app = QApplication(sys.argv)
class mainwindow1(QMainWindow):
def __init__(self):
super().__init__()
mainwindow = QWidget()
mainwindow.setObjectName("mainwindow")
mainwindow.setContentsMargins(100, 100, 100, 100)
self.setCentralWidget(mainwindow)
global pellow
global scanProgressbar
scanProgressbar = QProgressBar()
pellow = QPushButton('Click Here')
pellow.setObjectName("defaultbutton")
cpscanlayout = QVBoxLayout(mainwindow)
cpscanlayout.addWidget(scanProgressbar, alignment=Qt.AlignCenter, stretch=10)
cpscanlayout.addWidget(pellow, alignment=Qt.AlignCenter, stretch=10)
cpscanlayout.setAlignment(Qt.AlignCenter)
# cpscanlayout.setContentsMargins(0, 0, 0, 0);
cpscanlayout.setSpacing(5);
pellow.setCursor(QCursor(Qt.PointingHandCursor))
pellow.clicked.connect(buttonclicked)
self.show()
app.exec_()
def buttonclicked():
global progressQueue
progressQueue = Queue()
thread = threading.Thread(target=childfunc, daemon=True, name="nemo", args=())
thread.start()
progressQueue.join()
while True:
if not progressQueue.empty():
msg = progressQueue.get()
scanProgressbar.setValue(msg)
return 0
The code has several errors such as imports (import queue but it doesn't look like it uses Queue, it imports Thread but it uses threading.Thread, etc).
On the other hand, the misconception is that in Qt you should not use code that blocks the eventloop, such as while True. If you want to exchange information between threads then in the case of Qt you should not use Queue since it does not notify when there is a new data, instead you must use the signals that are also thread-safe and do notify when there is new data.
import sys
import threading
import time
from PyQt5.QtCore import pyqtSignal, QObject, Qt
from PyQt5.QtGui import QCursor
from PyQt5.QtWidgets import (
QApplication,
QMainWindow,
QProgressBar,
QPushButton,
QVBoxLayout,
QWidget,
)
class Signaller(QObject):
progress_changed = pyqtSignal(int)
class mainwindow1(QMainWindow):
def __init__(self):
super().__init__()
mainwindow = QWidget()
mainwindow.setObjectName("mainwindow")
mainwindow.setContentsMargins(100, 100, 100, 100)
self.setCentralWidget(mainwindow)
scanProgressbar = QProgressBar()
pellow = QPushButton("Click Here")
pellow.setObjectName("defaultbutton")
cpscanlayout = QVBoxLayout(mainwindow)
cpscanlayout.addWidget(scanProgressbar, alignment=Qt.AlignCenter, stretch=10)
cpscanlayout.addWidget(pellow, alignment=Qt.AlignCenter, stretch=10)
cpscanlayout.setAlignment(Qt.AlignCenter)
# cpscanlayout.setContentsMargins(0, 0, 0, 0);
cpscanlayout.setSpacing(5)
pellow.setCursor(QCursor(Qt.PointingHandCursor))
pellow.clicked.connect(self.buttonclicked)
self.signaller = Signaller()
self.signaller.progress_changed.connect(scanProgressbar.setValue)
def buttonclicked(self):
thread = threading.Thread(
target=childfunc, args=(self.signaller,), daemon=True, name="nemo"
)
thread.start()
def childfunc(signaller):
for i in range(1, 100):
signaller.progress_changed.emit(i)
time.sleep(0.1)
app = QApplication(sys.argv)
w = mainwindow1()
w.show()
app.exec_()

Unable to run pyshark.FileCapture(pcap) inside a QThread class in linux Ubuntu 16.04

I have a PySide2 Application that works in windows. What it does is that it opens a network packet (pcap) file using the python pyshark library inside a QThread class. It is able to open that pcap file but when i tried to run the same app on ubuntu, it throws an error. RuntimeError: Cannot add child handler, the child watcher does not have a loop attached.
I have searched online for solutions and stumbled across this site -> https://github.com/KimiNewt/pyshark/issues/303
It seems that pyshark can only run in the main thread and not in the sub-thread.
I have also created a minimal example that replicates my problem as shown below.
import sys
import pyshark
import asyncio
from PySide2.QtCore import QThread, Qt, Slot
from PySide2.QtWidgets import QApplication, QMainWindow, QFrame, QHBoxLayout, QPushButton
class SubThread(QThread):
def __init__(self):
super(SubThread, self).__init__()
self.Input_file = "profibus.pcap"
def run(self):
print("thread is running!")
cap = pyshark.FileCapture(self.Input_file)
iter_obj = iter(cap)
pkt = next(iter_obj)
print(pkt)
cap.close()
class TopLayout(QFrame):
def __init__(self, sub_thread):
self.frame = QFrame()
self.layout = QHBoxLayout()
self.sub_thread = sub_thread
self.toggle_button_box = QHBoxLayout()
self.toggle_button = QPushButton("Start")
self.toggle_button.setCheckable(True)
self.toggle_button_box.addWidget(self.toggle_button)
self.layout.addLayout(self.toggle_button_box)
self.layout.setAlignment(Qt.AlignCenter)
self.frame.setLayout(self.layout)
self.toggle_button.clicked.connect(self.on_toggle_button_clicked)
#Slot()
def on_toggle_button_clicked(self):
self.sub_thread.start()
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.SubThread = SubThread()
self.top_layout = TopLayout(self.SubThread)
self.setCentralWidget(self.top_layout.frame)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
# Execute application
sys.exit(app.exec_())
My current environment configs are:
Python 3.7,
Pyside2 5.13.2,
Pyshark 0.4.2.9 (latest)

execute ping command with GUI

While writing a GUI application in PyQt5 I encounter weird(for me) behavior.
When I wanted to open an information window and start doing another thing after it fully loads. I noticed that the information window does not load fully until the next block of code is done.
This is how it looks
Code that reproduces this unwanted behavior
from PyQt5.QtWidgets import QApplication,QMessageBox
import sys
import os
app = QApplication(sys.argv)
box = QMessageBox()
box.setText("Text")
box.show()
os.system("ping 8.8.8.8 ")
sys.exit(app.exec())
Behavior is the same whether I use QMessegBox, inherit it in another class or write my own QMeesgeBox type class.
I guess this behavior works like this because of os.system() function and I would use Process or Thread to make a workaround, but if It is possible I would like to ensure that window is fully loaded and then the next procedure is taking place.
Python version 3.7.0
PyQt5 version 5.12.1
Although the solutions of S.Nick and Guimoute seems to work but the reality is that it has only made the window show a moment but if you want to interact with it you will see that it is frozen, for example try to move the window to check it. The os.system() task is blocking so it must be executed in another thread
import os
import sys
from PyQt5.QtWidgets import QApplication,QMessageBox
import threading
app = QApplication(sys.argv)
box = QMessageBox()
box.setText("Text")
box.show()
def task():
os.system("ping 8.8.8.8 ")
threading.Thread(target=task, daemon=True).start()
# or threading.Thread(target=os.system, args=("ping 8.8.8.8 ",), daemon=True).start()
sys.exit(app.exec_())
Or use QProcess:
import sys
import os
from PyQt5.QtWidgets import QApplication,QMessageBox
from PyQt5.QtCore import QProcess
app = QApplication(sys.argv)
box = QMessageBox()
box.setText("Text")
box.show()
def on_readyReadStandardOutput():
print(process.readAllStandardOutput().data().decode(), end="")
process = QProcess()
process.start("ping", ["8.8.8.8"])
process.readyReadStandardOutput.connect(on_readyReadStandardOutput)
sys.exit(app.exec_())
Update
import os
import sys
from PyQt5 import QtCore, QtWidgets
class PingObject(QtCore.QObject):
finished = QtCore.pyqtSignal()
#QtCore.pyqtSlot()
def start(self):
os.system("ping 8.8.8.8")
self.finished.emit()
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
box = QtWidgets.QMessageBox()
box.setText("Text")
box.show()
thread = QtCore.QThread()
thread.start()
ping = PingObject()
ping.moveToThread(thread)
QtCore.QTimer.singleShot(0, ping.start)
loop = QtCore.QEventLoop()
ping.finished.connect(loop.quit)
loop.exec_()
print("finished ping")
sys.exit(app.exec_())
Another Option:
import os
import sys
from PyQt5 import QtCore, QtWidgets
class Thread(QtCore.QThread):
def run(self):
response = os.popen("ping 8.8.8.8")
for line in response.readlines():
print(line)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
box = QtWidgets.QMessageBox()
box.setText("Text")
box.show()
thread = Thread()
thread.start()
ret = app.exec_()
thread.quit()
thread.wait()
sys.exit(ret)
Here is a single-line solution:
from PyQt5.QtWidgets import QApplication,QMessageBox
import sys
import os
app = QApplication(sys.argv)
box = QMessageBox()
box.setText("Text")
box.show()
QApplication.processEvents() # <------------ this one
os.system("ping 8.8.8.8 ")
sys.exit(app.exec())
As an option. Try it:
import sys
import os
from PyQt5.QtWidgets import QApplication,QMessageBox
from PyQt5.QtCore import QTimer
app = QApplication(sys.argv)
box = QMessageBox()
box.setText("Text")
box.show()
def osSystem():
os.system("ping 8.8.8.8 ")
QTimer.singleShot(20, osSystem )
#os.system("ping 8.8.8.8 ")
sys.exit(app.exec())

Run script from GUI

I wrote a script (test.py) for Data Analysis. Now I'm doing a GUI in PyQt.
What I want is when I press a button 'Run', the script test.py will run and show the results (plots).
I tried subprocess.call('test1.py') and subprocess.Popen('test1.py') but it only opens the script and don't run it.
I also tried os.system, doesn't work either.
The script below is not complete (there are more buttons and functions associated but is not relevant and aren't connect to the problem described).
I'm using Python 3.6 on Spyder and PyQt5.
Is there any other function or module that can do what I want?
class Window(QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.setGeometry(50, 50, 500, 300)
self.setWindowTitle("TEMP FILE")
self.home()
def home (self):
btn_run = QPushButton("Run", self)
btn_run.clicked.connect(self.execute)
self.show()
def execute(self):
subprocess.Popen('test1.py', shell=True)
subprocess.call(["python", "test1.py"])
if not QtWidgets.QApplication.instance():
app = QtWidgets.QApplication(sys.argv)
else:
app = QtWidgets.QApplication.instance()
GUI = Window()
app.exec_()
What you need to do is create a text label, then pipe stdout / stderr to subprocess.PIPE:
p = subprocess.Popen(
"python test1.py",
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
Then call subprocess.Popen.communicate():
stdout, stderr = p.communicate()
# Display stdout (and possibly stderr) in a text label
You can import test1.py and call functions from within it whenever you wish
Use this How can I make one python file run another?
QProcess class is used to start external programs and to communicate with them.
Try it:
import sys
import subprocess
from PyQt5 import Qt
from PyQt5.QtWidgets import QMainWindow, QApplication, QPushButton
from PyQt5.QtCore import QProcess
class Window(QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.setGeometry(50, 50, 500, 300)
self.setWindowTitle("TEMP FILE")
self.home()
def home (self):
btn_run = QPushButton("Run", self)
#btn_run.clicked.connect(self.execute) # ---
filepath = "python test1.py" # +++
btn_run.clicked.connect(lambda checked, arg=filepath: self.execute(arg)) # +++
self.show()
def execute(self, filepath): # +++
#subprocess.Popen('test1.py', shell=True)
#subprocess.call(["python", "test1.py"])
# It works
#subprocess.run("python test1.py")
QProcess.startDetached(filepath) # +++
if not QApplication.instance():
app = QApplication(sys.argv)
else:
app = QApplication.instance()
GUI = Window()
app.exec_()

Categories

Resources