I have a PyQt window that calls multiple executables as QProcess. How can I list the outputs of each process after the last one has finished? (something like process_result = ["result1", "result2",..])
Let us say it looks like this:
for i in list_of_processes:
process = QtCore.QProcess()
process.start(i)
I can read with process.readyReadStandardOutput() somehow but it is quite chaotic because processes run parallel. process.waitForFinished() does not work because the GUI will freeze.
Also, I checked following page about multithreading: Multithreading PyQt applications with QThreadPool. Another question is similar but did not help me either: Pyside: Multiple QProcess output to TextEdit.
A possible solution is to create a class that manages the processes, and that emits a single signal when all the processes finish as you require.
import sys
from functools import partial
from PyQt4 import QtCore, QtGui
class TaskManager(QtCore.QObject):
resultsChanged = QtCore.pyqtSignal(list)
def __init__(self, parent=None):
QtCore.QObject.__init__(self, parent)
self.results = []
self.m_processes = []
self.number_process_running = 0
def start_process(self, programs):
for i, program in enumerate(programs):
process = QtCore.QProcess(self)
process.readyReadStandardOutput.connect(partial(self.onReadyReadStandardOutput, i))
process.start(program)
self.m_processes.append(process)
self.results.append("")
self.number_process_running += 1
def onReadyReadStandardOutput(self, i):
process = self.sender()
self.results[i] = process.readAllStandardOutput()
self.number_process_running -= 1
if self.number_process_running <= 0:
self.resultsChanged.emit(self.results)
def on_finished(results):
print(results)
QtCore.QCoreApplication.quit()
if __name__ == '__main__':
app = QtCore.QCoreApplication(sys.argv)
manager = TaskManager()
manager.start_process(["ls", "ls"])
manager.resultsChanged.connect(on_finished)
sys.exit(app.exec_())
Related
I am useing python 3.7 and pyqt5
What I want to do is run same QObject with different thread many times.
here is my main
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import threading
import sys
from Worker_Starting import Worker_Starting
class Main(QMainWindow):
def __init__(self) :
super().__init__()
self.setupUI()
self.initSignal()
def setupUI(self):
self.resize(400, 400)
self.pushButton = QPushButton("Start", self)
self.pushButton_2 = QPushButton("Stop", self)
self.pushButton.move(0,0)
self.pushButton_2.move(120,0)
def initSignal(self) :
self.pushButton.clicked.connect(self.Start)
self.pushButton_2.clicked.connect(self.Stop)
#pyqtSlot()
def Start(self) :
for i in range(3) :
print('main', threading.get_ident())
input = ["userID", "userNAME"]
self.my_thread = QThread()
self.my_worker = Worker_Starting(passing_list=input)
self.my_worker.moveToThread(self.my_thread)
self.my_thread.started.connect(self.my_worker.my_fn)
self.my_thread.start()
time.sleep(3)
#pyqtSlot()
def Stop(self) :
pass
if __name__ == "__main__":
app = QApplication(sys.argv)
window = Main()
window.show()
app.exec_()
and my Qobject
from PyQt5 import QtCore
from PyQt5.QtCore import *
import time
import threading
class Worker_Starting(QObject):
def __init__(self, passing_list, parent=None):
QtCore.QThread.__init__(self, parent=parent)
self.userid = passing_list[0]
self.username = passing_list[1]
def my_fn(self):
print(self.username, threading.get_ident())
for i in range(10) :
Now = time.strftime("%Y/%m/%d %H:%M:%S")
print(str(i) + " : " + self.username + " running" + " : " + Now)
time.sleep(0.5)
I put for i in range(3): under def start(self):
because in my actual main script other thread emit input to def start(self): several times.
if I emit only one time, def start(self): works fine.
but when I emit several times, it chrashs.
since I have to emit several times and I need my_thread to work simultaneously and continuously
is there anyway to set same Qobject to different thread?
and how to know or set thread id.
and how to stop thread by thread id.
*I have tried QRunnable, I now it works multi thread. but I have to emit signal back continouously to my main. as far as I know QRunnable are not suitable for custom signal.
The problem is that you're constantly creating new threads by overwriting the existing one, which results in the previous being garbage collected before it's completed, hence the crash.
If you want to execute the same "worker" function concurrently, you cannot use a single object for more threads, as you should use moveToThread each time and this is not possible.
The solution is to use QRunnable instead, implement the function within its run() method, and call QThreadPool to run it.
class Worker_Starting(QRunnable):
def __init__(self, passing_list):
QRunnable.__init__(self)
self.setAutoDelete(False)
self.userid = passing_list[0]
self.username = passing_list[1]
def run(self):
print(self.username, threading.get_ident())
for i in range(10) :
Now = time.strftime("%Y/%m/%d %H:%M:%S")
print(str(i) + " : " + self.username + " running" + " : " + Now)
time.sleep(0.5)
class Main(QMainWindow):
def __init__(self) :
super().__init__()
self.setupUI()
self.initSignal()
self.worker = None
# ...
def Start(self) :
if not self.worker:
input = ["userID", "userNAME"]
self.worker = Worker_Starting(passing_list=input)
for i in range(3):
print('main', threading.get_ident())
QThreadPool.globalInstance().start(self.worker)
Remember that you should not use blocking functions within the main Qt thread (that's why I removed time.sleep(3) from Start()). If you want to start the function more than once at regular intervals, use a QTimer:
interval = 3000 # interval is in milliseconds
for i in range(3):
print('main', threading.get_ident())
QTimer.singleShot(interval * i, lambda:
QThreadPool.globalInstance().start(self.worker))
Note that the pyqtSlot() decorator is usually needed only for special cases and advanced implementation, so you should remove it. Also note that only classes and constants should have capitalized names, while functions, variables and attributes should be lower cased (prefer start and stop instead of Start and Stop).
I have a thread where I need to execute a heavy function.
First, I have a function that takes two QDateTime values from the GUI and convert them to UNIX timestamps. Secondly, the "heavy" function uses these values to perform a task.
Both functions (function_task, time_converter_to_unix) do not belong to any class, so as far as I know I can use them in the thread.
But not the parameters, since I cannot access the QDateTime values.
Error: AttributeError: 'TaskThread' object has no attribute 'startTime'
How can I access the QDateTime and read the content from the thread? Thank you.
EDIT: Complete Code. You can find also the link to the GUI in the following link: interface.ui
import sys
import datetime
import time
from PyQt4 import QtCore, QtGui, uic
from PyQt4.QtCore import *
from PyQt4.QtGui import *
# Link to GUI
qtCreatorFile = "interface.ui"
Ui_MainWindow, QtBaseClass = uic.loadUiType(qtCreatorFile)
def time_converter_to_unix(start_datetime, end_datetime):
# Convert QTimeEdit to UNIX Timestamp (int, msec included), and then to float
start_datetime_unix_int = start_datetime.toMSecsSinceEpoch ()
start_datetime_unix = (float(start_datetime_unix_int) / 1000)
end_datetime_unix_int = end_datetime.toMSecsSinceEpoch ()
end_datetime_unix = (float(end_datetime_unix_int) / 1000)
return start_datetime_unix, end_datetime_unix
def dummy_function(self, start_datetime_unix, end_datetime_unix):
# Dummy function, just to simulate a task
result = start_datetime_unix + end_datetime_unix
print result
class Tool(QtGui.QMainWindow, Ui_MainWindow):
def __init__(self, parent = None):
# Setting-ip UI
QtGui.QMainWindow.__init__(self)
Ui_MainWindow.__init__(self)
self.setupUi(self)
# Button Action
self.runButton.clicked.connect(self.onStart)
# Progress Bar and Label. At the begining, the bar is at 0
self.progressBar.setValue(0)
self.progressBar.setRange(0,100)
self.resultLabel.setText("Waiting...")
#Thread
self.myLongTask = TaskThread()
self.myLongTask.taskFinished.connect(self.onFinished)
def onStart(self):
# Before running the thread, we set the progress bar in waiting mode
self.progressBar.setRange(0,0)
self.resultLabel.setText("In progress...")
print "Starting thread..."
self.myLongTask.start()
def onFinished(self):
# Stop the pulsation when the thread has finished
self.progressBar.setRange(0,1)
self.progressBar.setValue(1)
self.resultLabel.setText("Done")
class TaskThread(QtCore.QThread):
taskFinished = QtCore.pyqtSignal()
def __init__(self):
QtCore.QThread.__init__(self)
def __del__(self):
self.wait()
def run(self):
# First, we read the times from the QDateTime elements in the interface
print "Getting times..."
start_datetime_unix, end_datetime_unix = time_converter_to_unix(self.startTime.dateTime(), self.endTime.dateTime())
# Then, we put these values in my_function
print "Executing function..."
dummy_function(self, start_datetime_unix, end_datetime_unix)
# To finish, we execute onFinished.
print "Finishing thread..."
self.taskFinished.emit()
def main():
app = QtGui.QApplication(sys.argv)
window = Tool()
window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
If you execute it along with the .ui, you will see, that once we click on "Run", the Progress bar stays in waiting mode, and the error commented above appears.
startTime and endTime belong to the GUI, and not to the thread, that's why you get that error.
On the other hand it is advisable not to access the GUI from the other thread, it is best to obtain the values before starting the thread and set it as property of the thread as shown below:
class Tool(QtGui.QMainWindow, Ui_MainWindow):
...
def onStart(self):
# Before running the thread, we set the progress bar in waiting mode
self.progressBar.setRange(0,0)
self.resultLabel.setText("In progress...")
self.myLongTask.start_dt = self.startTime.dateTime() # <----
self.myLongTask.end_dt = self.endTime.dateTime() # <----
print "Starting thread..."
self.myLongTask.start()
...
class TaskThread(QtCore.QThread):
...
def run(self):
# First, we read the times from the QDateTime elements in the interface
print "Getting times..."
start_datetime_unix, end_datetime_unix = time_converter_to_unix(self.start_dt, self.end_dt) # <----
# Then, we put these values in my_function
print "Executing function..."
dummy_function(self, start_datetime_unix, end_datetime_unix)
# To finish, we execute onFinished.
print "Finishing thread..."
self.taskFinished.emit()
The context:
I'm building a Graphical Interface with Qt creator and the "behaviour" file in python. A test version of my GUI is:
The expected behaviour:
I am running 2 different threads which are referred to the same function with different input arguments. With the SELECTOR button I can assign the value of 1 or 2 to a variable (and display it)
The button Start thread enables the correct thread to start (the first time).
The loop should be turned off by the stop button by modifying the global running variable.
This is my code
# -*- coding: utf-8 -*-
from PyQt4 import QtCore, QtGui, uic
import sys
import threading
import time
import Queue
running = False
first_thread = None
second_thread = None
form_class = uic.loadUiType("simple2.ui")[0]
q = Queue.Queue()
select = 0
def action(string, queue): #function called by threads
global running
while(running):
phrase = string
if queue.qsize() < 10:
queue.put(phrase)
#else:
# print queue.qsize()
class MyWindowClass(QtGui.QMainWindow, form_class):
def __init__(self, parent=None):
QtGui.QMainWindow.__init__(self, parent)
self.setupUi(self)
#buttons
self.startButton.clicked.connect(self.start_clicked)
self.stopButton.clicked.connect(self.stop_clicked)
self.selector.clicked.connect(self.sel_click)
#variables
self.first = False
self.second = False
#queue
self.timer = QtCore.QTimer(self)
self.timer.timeout.connect(self.update_phrase)
self.timer.start(1)
def start_clicked(self): #start button callback
global select
if select > 0:
global running
running = True
print "started"
if (not self.first) & (select == 1):
first_thread.start()
self.first = True
if (not self.second) & (select == 2):
second_thread.start()
self.second = True
self.startButton.setEnabled(False)
self.startButton.setText('Starting...')
def stop_clicked(self): #stop button callback
global running
running = False
print "stopped"
self.startButton.setEnabled(True)
self.startButton.setText('Start Thread')
def sel_click(self): #selector button callback
global select
if select < 2:
select = select + 1
else:
select = 1
self.thread_counter.setText(str(select))
def update_phrase(self): #looping function
global running
if (not q.empty()) & running:
self.startButton.setText('Thread on')
abc = q.get()
print abc
def closeEvent(self, event):
global running
running = False
if __name__ == "__main__":
first_thread = threading.Thread(target=action, args = ("first", q))
second_thread = threading.Thread(target=action, args = ("second", q))
app = QtGui.QApplication(sys.argv)
w = MyWindowClass(None)
w.setWindowTitle('Multiple threads test in python')
w.show()
app.exec_()
For now, each thread should simple print on terminal their arguments ("First" or "Second").
If threads are started for the first time, my code works. But I would like to switch between threads infinite times.
Since threads cannot be stopped, is there a way to "pause" them?
I cannot find a solution, I hope someone will help me also with a piece of code. Thank you in advance
You can use Lock class to do that, a simple example would be:
import threading
lock = threading.Lock()
//here it will be lock
lock.acquire() # will block if lock is already held
...
then in other side do
//this will wake up
lock.release()
you can read more here http://effbot.org/zone/thread-synchronization.htm
I'm trying to understand why the following code immediately exits, but works if I create the thread in the main context and not in a second object?
from PyQt4 import QtCore
import time
import sys
class SomeObject(QtCore.QObject):
finished = QtCore.pyqtSignal()
def longRunning(self):
count = 0
while count < 5:
time.sleep(1)
print "Increasing"
count += 1
self.finished.emit()
class SecondObject(QtCore.QObject):
def __init__(self, app):
QtCore.QObject.__init__(self)
objThread = QtCore.QThread()
obj = SomeObject()
obj.moveToThread(objThread)
obj.finished.connect(objThread.quit)
objThread.started.connect(obj.longRunning)
objThread.finished.connect(app.exit)
objThread.start()
def usingMoveToThread():
app = QtCore.QCoreApplication([])
SecondObject(app)
sys.exit(app.exec_())
if __name__ == "__main__":
usingMoveToThread()
Thanks in Advance for any help!
I've found the problem. Apparently you need to hold on to the new Thread's reference otherwise the Application will just quit...
I'm trying to build a PyQt app which (among other things) has the ability via a QTextEdit Box to function like a serial terminal program (HyperTerminal, TeraTerm, etc.) I've read through a few examples from the PySerial page and I think I've managed to get the receive data thread working properly but maybe not as efficiently as possible.
My problem is how do I take the last typed character in the QTextEdit box and send that out the serial connection? I've tried using the textChanged signal that QTextEdit emits, but that then sends everything that I type AND that it receives. I've tried setting up an eventFilter in my main GUI class, but I can't figure out how to get that over to the serial function in another file. Do I want to have a separate thread that listens for a signal emitted from the eventFilter? How do I do that? Is there a more elegant way to do this?
I'm sure I've just managed to overthink this and the solution is simple, but I'm somewhat struggling with it. I'll attach the relevant code snippets (not a full code set) and perhaps somebody can point me in the right direction. If anybody also thinks that the threading that I'm doing could be done in a more efficient manner, then please relay that to me as well!
Thanks for any help that anybody can provide!
Main File:
import sys
from PyQt4 import QtGui
from MainGUI import TestGUI
from SerialClasses import *
from SerialMiniterm import *
class StartMainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super(StartMainWindow, self).__init__(parent)
self.ui = TestGUI()
self.ui.setupUi(self)
self.ui.serialTextEditBox.installEventFilter(self)
def eventFilter(self, source, event):
if (event.type() == QtCore.QEvent.KeyPress and source is self.ui.serialTextEditBox):
# print some debug statements to console
if (event.key() == QtCore.Qt.Key_Tab):
print ('Tab pressed')
print ('key pressed: %s' % event.text())
print ('code pressed: %d' % event.key())
# do i emit a signal here? how do i catch it in thread?
self.emit(QtCore.SIGNAL('transmitSerialData(QString)'), event.key())
return True
return QtGui.QTextEdit.eventFilter(self, source, event)
def serialConnectCallback(self):
self.miniterm = SerialMiniterm(self.ui, self.SerialSettings)
self.miniterm.start()
temp = self.SerialSettings.Port + 1
self.ui.serialLabel.setText("<font color = green>Serial Terminal Connected on COM%d" % temp)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
app.setStyle("Cleanlooks")
myapp = StartMainWindow()
myapp.show()
sys.exit(app.exec_())
SerialMiniterm.py:
import serial
from PyQt4 import QtGui, QtCore
def character(b):
return b
class SerialMiniterm(object):
def __init__(self, ui, SerialSettings):
self.SerialSettings = SerialSettings
self.ui = ui
self.serial = serial.Serial(self.SerialSettings.Port, self.SerialSettings.BaudRate, parity=self.SerialSettings.Parity, rtscts=self.SerialSettings.RTS_CTS, xonxoff=self.SerialSettings.Xon_Xoff, timeout=1)
self.repr_mode = self.SerialSettings.RxMode
self.convert_outgoing = self.SerialSettings.NewlineMode
self.newline = NEWLINE_CONVERISON_MAP[self.convert_outgoing]
self.dtr_state = True
self.rts_state = True
self.break_state = False
def _start_reader(self):
"""Start reader thread"""
self._reader_alive = True
self.receiver_thread = ReaderThread(self.alive, self._reader_alive, self.repr_mode, self.convert_outgoing, self.serial)
self.receiver_thread.connect(self.receiver_thread, QtCore.SIGNAL("updateSerialTextBox(QString)"), self.updateTextBox)
self.receiver_thread.start()
def _stop_reader(self):
"""Stop reader thread only, wait for clean exit of thread"""
self._reader_alive = False
self.receiver_thread.join()
def updateTextBox(self, q):
self.ui.serialTextEditBox.insertPlainText(q)
self.ui.serialTextEditBox.moveCursor(QtGui.QTextCursor.End)
#print "got here with value %s..." % q
def start(self):
self.alive = True
self._start_reader()
# how do i handle transmitter thread?
def stop(self):
self.alive = False
def join(self, transmit_only=False):
self.transmitter_thread.join()
if not transmit_only:
self.receiver_thread.join()
class ReaderThread(QtCore.QThread):
def __init__(self, alive, _reader_alive, repr_mode, convert_outgoing, serial, parent=None):
QtCore.QThread.__init__(self, parent)
self.alive = alive
self._reader_alive = _reader_alive
self.repr_mode = repr_mode
self.convert_outgoing = convert_outgoing
self.serial = serial
def __del__(self):
self.wait()
def run(self):
"""loop and copy serial->console"""
while self.alive and self._reader_alive:
data = self.serial.read(self.serial.inWaiting())
if data: #check if not timeout
q = data
self.emit(QtCore.SIGNAL('updateSerialTextBox(QString)'), q)
Something like this?
from PyQt4 import QtCore, QtGui
app = QtGui.QApplication([])
class Terminal(QtGui.QPlainTextEdit):
def keyPressEvent(self, event):
print event.text()
return QtGui.QPlainTextEdit.keyPressEvent(self, event)
term = Terminal()
term.show()