I'm trying to figure out how to interact with the main thread in PyQt5 from another thread running in another file.
In the example below I'm trying to make a button green from the followup.py file's initialize function which is a thread started in the workbot_main.py file.
From the followup.py file I can't call 'workbot_main.widget' because it doesn't recognize it and I can't call workbot_main.MainWindow because it doesn't recognize it as an instantiated class so 'self' doesn't work and therefore most things within the class.
How am I supposed to interact with the MainWindow thread from another file?
I tried using slots and signals but I can't get that to work either.
Help would be massively appreciated.
#workbot_main.py
import threading
from workbot import *
import followup
from PyQt5 import QtWidgets
from PyQt5 import QtCore
from PyQt5.QtWidgets import QMainWindow, QApplication
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
#UI_MainWindow is from the workbot.py file which is generated from QTDesigner
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
followup_start_button = self.ui.followup_start_button
followup_start_button.clicked.connect(threads.thread_launch_followup_initialize)
p1_followup_button = self.ui.p1_followup_button
#QtCore.pyqtSlot()
def p1_followup_button_color_green():
self.p1_followup_button.setStyleSheet("background-color : green")
class Threads:
def __init__(self):
pass
def thread_launch_followup_initialize(self):
t1 = threading.Thread(target = followup.initialize, args = ())
t1.start()
threads = Threads()
if __name__ == '__main__':
app = QtWidgets.QApplication([])
widget = MainWindow()
widget.show()
app.exec_()
#followup.py file
def make_green():
workbot_main.MainWindow.p1_followup_button_color_green()
initialize():
make_green()
do other stuff
Edit:
I've tried doing a great many things, the only thing I could get to work is this
#workbot_main.py
import threading
from workbot import *
import followup
from PyQt5 import QtWidgets
from PyQt5 import QtCore
from PyQt5.QtWidgets import QMainWindow, QApplication
from PyQt5.QtCore import QThread, pyqtSignal, QObject, pyqtSlot, Qt
class Change_green(QObject):
setgreen = pyqtSignal()
#pyqtSlot()
def green(self):
self.setgreen.emit()
print("clicked")
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
#UI_MainWindow is from the workbot.py file which is generated from QTDesigner
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.Change_green = Change_green()
self.Change_green.setgreen.connect(lambda: print("connected"))
self.Change_green.setgreen.connect(self.p1_followup_button_color_green)
followup_start_button = self.ui.followup_start_button
followup_start_button.clicked.connect(self.Change_green.green) # <--- this works
p1_followup_button = self.ui.p1_followup_button
#pyqtSlot()
def p1_followup_button_color_green():
self.p1_followup_button.setStyleSheet("background-color : green")
if __name__ == '__main__':
app = QtWidgets.QApplication([])
widget = MainWindow()
widget.show()
app.exec_()
But that is useless to me since the signal is sent from inside the main thread.
What I need to work is this
#workbot_main.py
import threading
from workbot import *
import followup
from PyQt5 import QtWidgets
from PyQt5 import QtCore
from PyQt5.QtWidgets import QMainWindow, QApplication
from PyQt5.QtCore import QThread, pyqtSignal, QObject, pyqtSlot, Qt
class Change_green(QObject):
setgreen = pyqtSignal()
#pyqtSlot()
def green(self):
self.setgreen.emit()
print("clicked")
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
#UI_MainWindow is from the workbot.py file which is generated from QTDesigner
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.Change_green = Change_green()
self.Change_green.setgreen.connect(lambda: print("connected"))
self.Change_green.setgreen.connect(self.p1_followup_button_color_green)
followup_start_button = self.ui.followup_start_button
#starts the initialize function in followup.py which calls the Change_green class above
followup_start_button.clicked.connect(threads.thread_launch_followup_initialize)
p1_followup_button = self.ui.p1_followup_button
#pyqtSlot()
def p1_followup_button_color_green():
self.p1_followup_button.setStyleSheet("background-color : green")
class Threads:
def __init__(self):
pass
def thread_launch_followup_initialize(self):
t1 = threading.Thread(target = followup.initialize, args = ())
t1.start()
threads = Threads()
if __name__ == '__main__':
app = QtWidgets.QApplication([])
widget = MainWindow()
widget.show()
app.exec_()
# followup.py
import workbot_main
color = workbot_main.Change_green()
def initialize():
print("initializing")
color.green()
But if I call the Change_green() function from followup.py, the Change_green() function gets called properly because "clicked" gets printed but there is no following "connected" being printed from the main thread.
It's as if self.setgreen.emit() only works when called from within the main thread.
I couldn't execute your script since you used a QTDesigner project to create the GUI. Also, I don't know which modules are workbot or followup, so I didn't tried to install them as they might be private modules.
I can see that you used the threading module from python. In many tutorials available on the internet, they tell you to not use this module when dealing with Qt. If need multithreading, use the QThread class instead.
The reason is that Qt schedules its own events. When dealing with raw data, that might be ok. But when dealing with QWidgets, their methods are not thread safe, so executing them from another thread might lead you to many undesired problems.
As I recommend using QThreads, I provided a minor example below explaining how to use it (it's not so easy to start using QThreads, I had and still have a hard time when dealing with them).
If you need, you can place the QThread and QObject classes in another file, then import them from main file. There's no problem about that, just be careful to not let their instances' references be collected by the python's garbage collector.
I keep them out of reach by using self.thread = ChangeColorThread(...) inside the Scene's constructor. In this way, the thread's instance is bound to the Scene, and it's reference will not go out of scope when the Scene's constructor block ends.
from PySide2.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QLabel
from PySide2.QtCore import QObject, QThread, Signal, QMutex
import random
# I use PySide2 which is the same thing as PyQt5 with the exception of a few variable names
# that might change from one module to another. One of them might be `Signal` (PySide2) to
# `pyqtSignal` (PyQt5) for example.
#
# The imports should also change:
# from PySide2.QtWidgets import ...
# to
# from PyQt5.QtWidgets import ...
#
# But the idea is the same for both modules.
# -------------------------------------------------------------------------------------
# After a few researches on the Multi-Threading topic, I liked the option of using the
# `QObject.moveToThread(thread)` method to create threads based on QObjects.
#
# There are others:
# - Using QThreadPool and QRunnable
# - Using QThread only (by overwriting run() method. Not recommended.)
#
# For me, I got everything working so far using the `moveToThread` method, so that's
# the one I will be using on this example.
# -------------------------------------------------------------------------------------
# Here we create the Worker Object. Everything inside `Worker.main(self)` will be
# executed in another thread: ChangeColorThread.
#
# As we might inspect / change the value of `Worker.running` on both threads at the same time
# (Main Thread and ChangeColorThread), I recommend using a QMutex to restrict access
# to this variable.
class Worker(QObject):
# Note the signal possesses a `object` as a parameter. It signalizes the QObject
# that you want to pass an object type to the signal's receiver.
#
# In this case, it's a tuple (red, green, blue) as `Scene.setRandomColor` has the
# `color` parameter.
produce = Signal(object)
# Called from main thread only to construct the Worker instance.
def __init__(self, delay):
QObject.__init__(self)
self.running = True
self.delay = delay
self.runningLock = QMutex()
# Might be called from both threads.
def stop(self):
self.runningLock.lock()
self.running = False
self.runningLock.unlock()
# Might be called from both threads.
def stillRunning(self):
self.runningLock.lock()
value = self.running
self.runningLock.unlock()
return value
# Executes on ChangeColorThread only.
def main(self):
while (self.stillRunning()):
# Generate the random colors from the ChangeColorThread.
red = random.randint(0, 255)
green = random.randint(0, 255)
blue = random.randint(0, 255)
# Emit the `produce` signal to safely call the `Scene.setRandomColor()`
# on the Main Thread.
#
# In order to update any GUI or call any QWidget method, you must emit
# a connected signal to the main thread, so you don't raise any exceptions
# or segmentation faults (or worse, silent crashes).
self.produce.emit((red, green, blue))
# Tell the ChangeColorThread to sleep for delay microseconds
# (aka a value of 1000 == 1 second)
self.thread().msleep(self.delay)
print('Quit from ChangeColorThread Worker.main()')
self.thread().quit()
# Here we have the other thread we will instantiate. The thread by itself
# is nothing special. It acts like a QObject until `QThread.start()` is
# called. It also has a few important properties you might want to take a look:
# - started (calls one or more connected functions once the thread starts executing).
# - finished (calls one or more connected functions once the thread properly finishes).
#
# When `start()` is called, as we connected `self.started` to `Worker.main`,
# `Worker.main` will start executing on the other thread until a `stop` call
# is requested.
class ChangeColorThread(QThread):
def __init__(self, produce_callback, delay=100):
QThread.__init__(self)
self.worker = Worker(delay)
self.worker.moveToThread(self)
self.started.connect(self.worker.main)
self.worker.produce.connect(produce_callback)
# Stops the worker object's main loop from the main thread.
def stop(self):
self.worker.stop()
# Here we will create the GUI. I kept it simple, it contains a single QLabel.
#
# It also starts the ChangeColorThread thread.
class Scene(QWidget):
def __init__(self):
QWidget.__init__(self)
layout = QVBoxLayout()
self.setLayout(layout)
self.label = QLabel("<h3>See the Color Change?</h3>")
layout.addWidget(self.label)
self.thread = ChangeColorThread(self.setRandomColor, 500)
self.thread.start()
# This function will execute on the Main Thread only.
#
# However, this function is not called by the user. It is scheduled to execute at some point
# by PyQt5 once `Worker.produce` is emitted.
def setRandomColor(self, color):
self.label.setStyleSheet("background-color:rgb(%d,%d,%d)" % (color[0], color[1], color[2]))
# As we don't know wether ChangeColorThread has stopped or not, we signalize
# it to stop before closing the application.
#
# This step is important, because even if PyQt5 / PySide2 tries its best to close the
# remaining thread, once the window is closed, this process can fail. The result is a
# hidden process still running even after the program is closed.
def sceneInterceptCloseEvent(self, evt):
self.thread.stop()
self.thread.wait()
evt.accept()
# Here we create the Main Window. It contains a single scene with the QLabel inside it.
class Window(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.scene = Scene()
self.setCentralWidget(self.scene)
# We call Scene.sceneInterceptCloseEvent in order to signalize the running thread
# to stop its main loop before the application is closed.
def closeEvent(self, evt):
self.scene.sceneInterceptCloseEvent(evt)
if __name__ == '__main__':
app = QApplication()
win = Window()
win.show()
app.exec_()
So the idea of this example is simple. We have 2 threads:
The Main Thread: which handles the GUI, scene creation, user input, and thread management. Whenever the user tries to exit the application, it's on this thread that the other ones will be signalized to be stopped.
ChangeColorThread: which is an infinite loop that executes on the background, to change the QLabel's color at every few seconds, set by the delay variable. It can be signalized to stop by the Main Thread.
Once the application starts executing, the label's background color should change based on the emission of Worker.produce signal, which sets which random color should be used for the label.
You can also move the window or resize it. By doing these manual operations, it proves that the ChangeColorThread is really executing at the background of the application. Otherwise the application would become unresponsive (frozen).
As stated on the script, I used PySide2 instead of PyQt5. But its almost the same thing, as just a few variable names should change from one to another. If you find any problems because of this, just let me know on the comments.
Edit: How to make your script work with both ways
What confused me about your post was that you rewrote your workbot_main.py script 3x, then wrote followup.py 2x, one at the middle and one at the end. I was not understanding your examples. It's hard to keep track of a lot of code, specially if you provide a whole script when you just change one line or another.
After checking them with more paciency, I was able to understand what you wanted to do. I think...
You're trying to this right?
You click on self.ui.followup_start_button
This button is connected to threads.thread_launch_followup_initialize
Which calls Change_green.green()
Which emits the signal setgreen
setgreen is connected to MainWindow.p1_followup_button_color_green
This function on MainThread changes the background color of self.ui.p1_followup_button to the green color.
The main problem I found with your script is that you're creating two different instances of Change_green:
First one: in your followup.py file.
Second one: inside your MainWindow.__init__(self).
So everytime that you tried to access the object created on MainWindow from your thread, you were in fact accessing a different instance of the same class. After solving this problem and making a few tweaks on your script, it worked without any errors so far. (Yes, the button's background color did changed to green.)
Also, you say that you can't solve your problem using QThreads. In the final example below, I also use your own classes (with one additional method and Signal) and a custom QThread I created, to reproduce the same type of idea, but now changing the button color to red.
I put all Threading Classes into followup.py, and left only Scene and Logic Classes on workbot_main.py.
Here is your new workbot_main.py:
# from workbot import * # Can't import from workbot as I don't have this module on my side
import followup
from PySide2 import QtWidgets
from PySide2 import QtCore
from PySide2.QtWidgets import QMainWindow, QApplication, QWidget, QPushButton, QVBoxLayout
from PySide2.QtCore import QThread, Signal, QObject, Slot, Qt
class Ui(QWidget):
def __init__(self):
QWidget.__init__(self)
layout = QVBoxLayout()
self.setLayout(layout)
self.followup_start_button = QPushButton("Start FollowUp (Green)")
self.qthread_button = QPushButton("Start QThread (Red)")
self.p1_followup_button = QPushButton("Check me Turn Out Green or Red")
layout.addWidget(self.followup_start_button)
layout.addWidget(self.qthread_button)
layout.addWidget(self.p1_followup_button)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# --------------------------------------------
# # As I don't have your QTDesigner file, I cannot
# # load it.
# # UI_MainWindow is from the workbot.py file which is generated from QTDesigner:
# self.ui = Ui_MainWindow()
# self.ui.setupUi(self)
# --------------------------------------------
# So I created my own "UI" to load both buttons on the screen
# and execute your script.
self.ui = Ui()
self.setCentralWidget(self.ui)
# --------------------------------------------
# threading module (green)
# --------------------------------------------
self.Change_green = followup.Change_green()
self.Change_green.setgreen.connect(lambda: print("connected green"))
self.Change_green.setgreen.connect(self.p1_followup_button_color_green)
# Starts the initialize function in followup.py which calls the Change_green class above
#
# After creating the Change_green instance, we initialize the thread
# from another file here, and pass the instance to the thread, so it
# can access it from there.
self.threads = followup.Threads(self.Change_green)
self.followup_start_button = self.ui.followup_start_button
self.followup_start_button.clicked.connect(self.threads.thread_launch_followup_initialize)
# This line was wrong. But you probably meant to assign this variable to
# your class. After fixing it your script does not raises any error.
self.p1_followup_button = self.ui.p1_followup_button
# --------------------------------------------
# QThread class (red)
# --------------------------------------------
# With QThread you solve this in one line given that everying is configured
# on MyThread(QThread) class.
self.qthread = followup.MyThread(self.Change_green, self.qthread_button_color_red)
self.ui.qthread_button.clicked.connect(self.qthread.start)
# I Changed pyqtSlot -> Slot (As I use PySide2 instead of PyQt5)
#Slot()
def p1_followup_button_color_green(self):
self.p1_followup_button.setStyleSheet("background-color: green")
# I Changed pyqtSlot -> Slot (As I use PySide2 instead of PyQt5)
#Slot()
def qthread_button_color_red(self):
self.p1_followup_button.setStyleSheet("background-color: red")
if __name__ == '__main__':
app = QtWidgets.QApplication([])
widget = MainWindow()
widget.show()
app.exec_()
Here is your new followup.py:
from PySide2.QtCore import QObject, QThread, Signal, Slot
import threading
# ------------------------------------------------------------
# - Fixing your script to use threading module
# ------------------------------------------------------------
# We don't need to import workbot_main anymore.
#
# `color` is the same object being referenced from `MainWindow.Change_green`,
# which was passed by parameter when the thread was initialized on
# `MainWindow.__init__(self)`.
#
# Now the code should work as expected.
def initialize(color):
print("initializing: ", color)
color.green()
# To make the example simpler, I've put all threading scripts here on followup.py
class Threads:
def __init__(self, color):
self.color = color
def thread_launch_followup_initialize(self):
t1 = threading.Thread(target=initialize, args=[self.color])
t1.start()
# Including this object here which will run in another thread.
#
# As I use PySide2 I changed
# "pyqtSignal" -> "Signal"
# "pyqtSlot" -> "Slot"
class Change_green(QObject):
setgreen = Signal()
setred = Signal()
#Slot()
def green(self):
self.setgreen.emit()
print("clicked on green")
#Slot()
def red(self):
self.setred.emit()
print("clicked on red")
self.thread().quit()
# ------------------------------------------------------------
# - Using QThreads instead of python threading module
# ------------------------------------------------------------
class MyThread(QThread):
def __init__(self, change_green, qthread_button_color_red):
QThread.__init__(self)
self.Change_green = change_green
self.Change_green.setred.connect(lambda: print("connected red"))
self.Change_green.setred.connect(qthread_button_color_red)
# This is important. It sets the QObject to run on another thread.
self.Change_green.moveToThread(self)
# Set Change_green.red method to be runned on the other thread
self.started.connect(self.Change_green.red)
# Set the main thread to wait for the other thread, once this
# even is fired.
self.Change_green.setred.connect(self.wait)
Again, I still use PySide2 instead of PyQt5, so instead of using pyqtSignal and pyqtSlot, I used Signal and Slot. This change also happened on the module imports.
I left the QThread example here (Original Answer before the edit) in case you want to compare both of them.
I want use a parallel downloading videos from youtube, but my code ending with exception "PicklingError". Can you help guys with code, how it should be, please.
Another fixed variant:
import sys
#from pathos.multiprocessing import ProcessingPool as Pool
from multiprocessing import Pool
from pytube import YouTube
from youtubeMultiDownloader import UiMainWindow
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QFileDialog
class YouTubeInstance:
def __init__(self, path):
self.youtube = YouTube
self.path = path
#self.ui_obj = ui_obj
def download_file(self, url):
self.youtube(url).streams.get_highest_resolution().download(self.path)
#self.ui.ui.youtube_outputs.setText(f'Video \'{self.youtube.title}\' has been downloaded successfully!')
class YouTubeMultiDownloader(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.pool = Pool
self.ui = UiMainWindow()
self.ui.setup_ui(self)
self.path_to_dir = None
self.urls = None
def _get_urls_from_form(self):
self.urls = self.ui.youtube_urls.toPlainText().split('\n')
return len(self.urls)
def choose_directory(self):
self.path_to_dir = str(QFileDialog.getExistingDirectory(self, "Select Directory"))
def run_multi_downloads(self):
youtube = YouTubeInstance(self.path_to_dir)
self.pool(self._get_urls_from_form()).map(youtube.download_file, self.urls)
if __name__ == "__main__":
app = QtWidgets.QApplication([])
application = YouTubeMultiDownloader()
application.show()
sys.exit(app.exec_())
Updated:
My ui :)
Error 1 fixed:
Error 2 fixed:
Error 3 actual:
You've got the wrong side of the stick. Take a look at multiprocessing module documents. As it says, calling Pool method is for running multiple instance of same function simultaneously (in parallel). So call Pool method as many numbers you want, meanwhile your method does not any parameters, call it without any arguments:
with Pool(5) as p:
print(p.map(YouTubeMultiDownloader))
It create 5 parallel instance. You can change the code an refine your errors.
I am new to python and multiprocessing (Electronics background). I am writing an GUI application to communicate with micro controller over serial port. The interaction with user is through some buttons on the GUI, which sends some command on serial port to the MCU and displays data received from MCU in the same GUI(there can be data from MCU without any command being sent). I am using PyQt5 module and QT designer to design the UI. I converted the UI to py code using uic.compileUi. I am then calling this python file in my main code.
After reading through several documents and stack overflow posts I decided to use to QThreads for the application.
I am however facing issue with serial communication when being used inside thread (It works fine if used in a single main loop without threads).
My code is:
from PyQt5 import uic
from PyQt5.QtWidgets import QApplication,QWidget,QMainWindow #
from PyQt5.QtCore import QCoreApplication, QObject, pyqtSignal, pyqtSlot, QThread
import serial
from GUI import Ui_MainGUIWindow
import sys
serialString = ""
def ClosePort():
serialPort.close()
# print("port closed")
def OpenPort():
serialPort.open()
#print("port open")
def sendFV():
serialPort.write('fv\r'.encode())
def configSerial(port,baudRate):
global serialPort
serialPort=serial.Serial(port = port, baudrate=baudRate, bytesize=8, timeout=None, stopbits=serial.STOPBITS_ONE)
if not serialPort.isOpen():
serialPort.open()
class Worker(QObject):
finished = pyqtSignal()
dataReady = pyqtSignal(['QString'])#[int], ['Qstring']
print("workerinit")
def run(self):
print ("worker run")
while(1):
if(serialPort.in_waiting > 0):
serialString = serialPort.read()
self.dataReady.emit(str(serialString))
self.finished.emit()
class GUI(QWidget):
def __init__(self):
self.obj = Worker()
self.thread = QThread()
self.obj.dataReady.connect(self.onDataReady)
self.obj.moveToThread(self.thread)
self.obj.finished.connect(self.thread.quit)
self.thread.started.connect(self.obj.run)
self.thread.finished.connect(app.exit)
self.thread.start()
self.initUI()
def initUI(self):
#Form, Window = uic.loadUiType("TestGUI.ui")
#print(Form,Window)
#self.window = Window()
#self.form=Form()
self.window = QMainWindow()
self.form = Ui_MainGUIWindow()
self.form.setupUi(self.window)
self.form.FVButton.clicked.connect(sendFV)
print("guiinit")
self.window.show()
def onDataReady(self, serialString):
print("here")
print(serialString.decode('Ascii'))
self.form.plainTextOutput.insertPlainText((serialString.decode('Ascii')))
self.form.plainTextOutput.ensureCursorVisible()
if __name__ == '__main__':
configSerial("COM20",9600)
"""
app = QCoreApplication.instance()
if app is None:
app = QCoreApplication(sys.argv)
"""
app = QApplication(sys.argv)
form=GUI()
app.exec_()
sys.exit(ClosePort())
I get following error on line if(serialPort.in_waiting > 0):
File "C:\Users\...\Anaconda3\lib\site-packages\serial\serialwin32.py", line 259, in in_waiting
raise SerialException("ClearCommError failed ({!r})".format(ctypes.WinError()))
SerialException: ClearCommError failed (OSError(9, 'The handle is invalid.', None, 6))
I found this post talking about similar issue, but do know how exactly to implement the suggested solution in my code.
Also I get "kernel died" error multiple times when I run the code on spyder IDE. I added a check if I am creating multiple instances of QT application but that did not help.Running the code from anaconda command prompt works fine.
I made a pyqt5 script which uses python3.6
my scripts takes input from serial device and updates the GUI and the remote database and also take remote input from web browser and make necessary changes to the serial device and update the GUI accordingly.
I am using QTimer to run the 4 functions/methods in the background to achieve this and it's working. But the problem is that after receiving a manual input or remote input (from web browser) it takes almost 15-20 seconds to implement the necessary changes in the serial device and the GUI and it makes the GUI almost unresponsive (it responds after almost 15-20 seconds).
I tried QThread but it always results in multiple access on the serial port at the same time causing the script to crash. Please, help me in this regard.
from PyQt5.QtCore import QTimer, QThread
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.uic import loadUiType
import os
import sys
import time
import serial
import MySQLdb
from pathlib import Path
import json
ui, _ = loadUiType('myapplication.ui')
class MainApp(QMainWindow, ui):
def __init__(self):
QMainWindow.__init__(self)
self.setupUi(self)
def new_method1(self):
print("This is new_method1")
def new_method2(self):
print("This is new_method2")
def new_method3(self):
print("This is new_method3")
def new_method4(self):
print("This is new_method4")
def continuous_calling(self):
self.timer = QTimer()
self.timer.timeout.connect(self.new_method1)
self.timer.start(1000)
self.timer.timeout.connect(self.new_method2)
self.timer.start(1000)
self.timer.timeout.connect(self.new_method3)
self.timer.start(1000)
self.timer.timeout.connect(self.new_method4)
self.timer.start(1000)
def main():
app = QApplication(sys.argv)
window = MainApp()
window.move(0, -30)
window.show()
window.continuous_calling()
app.exec_()
if __name__ == '__main__':
main()
Here, i want to run new_method1, new_method2, new_method3 & new_method4 methods continuously in the background as long as the GUI is not exiting without freezing the GUI. Thanks.
additional information::
This is an example of other functions that manages direct user inputs from GUI(button clicks from the GUI). And these functions don't run in the background repeatedly. These functions are triggered only when buttons(in the GUI) are clicked.
def ser_conn_on_zero(self):
self.ser = serial.Serial(
port=self.comm_port,
baudrate=9600,
bytesize=serial.EIGHTBITS,
parity=serial.PARITY_EVEN,
stopbits=serial.STOPBITS_ONE,
timeout=1)
if self.ser.isOpen():
print("Open: " + self.ser.name)
self.ser.flushInput()
self.ser.flushOutput()
self.ser.close()
self.ser.open()
print('Connected to :' + self.comm_port)
print("Turning ON Relay Zero")
self.ser.write(b'\x46\x6D\x45\x63\x73\x4B\x6F\x6C\x00\x05\x00\x01\x00\x01\x00\r\n')
output = self.ser.readline().decode('iso-8859-1')
print("Serial Response: " + output)
self.ser.close()
print("Serial connection Closed for Relay Zero")
conn = MySQLdb.connect("XX.XX.XX.XX", "User2", "PASSWORD", "TestSite_001", use_unicode=True, charset="utf8")
cursor = conn.cursor()
try:
cursor.execute("update some_table set chn = %s where id = %s", (1, 1))
conn.commit()
except MySQLdb.Error as e:
print("ser_conn_on_zero: couldn't update the remote database : ", str(e))
cursor.close()
conn.close()
I try to create a PyQt application. In order to run process in background and keep the PyQt5 application available for new instruction, I want to use multiprocessing.
On the Windows OS, when I call a function from the Qt MainWindow class with multiprocessing.process, I have an error about pickling this class. But it is running find on Linux.
Here is an example:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
import multiprocessing
#
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
class Frame_main(QMainWindow):
def __init__(self, parent = None):
super(Frame_main, self).__init__(parent)
self.thrd_list=[]
self.initUI()
def initUI(self):
# Button
btn_run = QPushButton('Run', self)
btn_run.clicked.connect(lambda: self.ThdSomething(self.DoRun) ) #
btn_stop = QPushButton('Stop', self)
btn_stop.clicked.connect(self.DoStop)
### TEXT Edit
self.textEdit_term = QTextEdit("terminal: \n ")
self.textEdit_term.append("")
self.textEdit_term.setStyleSheet("color: rgb(255, 255, 255); background-color: black;")
self.textEdit_term.setLineWrapMode(QTextEdit.NoWrap)
self.textEdit_term.setToolTip(' terminal message ')
self.textEdit_term.setStatusTip('textEdit1')
### LAYOUT
Wid_main = QWidget() #
grid_major = QGridLayout() #
grid_major.addWidget( btn_run, 1, 5)
grid_major.addWidget( btn_stop, 2, 5)
Wid_main.setLayout(grid_major)
self.setCentralWidget(Wid_main)
### Affichage
self.show() #
#
def DoRun(self):
print('g starts')
time_start=time.time()
time_stop=time.time()
name='bob'
n=0
while time_stop-time_start <2 :
n+=1
time_stop=time.time()
time.sleep(0.8)
print ('hola', name,n, flush=True)
print('g stops')
def DoStop(self):
''' subourtine kill all the thread '''
print('stop action detected')
while len(self.thrd_list) > 0 :
print("Terminating the job: {}".format(self.thrd[-1].pid) )
os.kill(self.thrd[-1].pid, signal.SIGTERM)
self.thrd_list[-1].terminate()
self.thrd_list.pop()
def ThdSomething(self, job):
''' subourtine to create a new thread to do the job subroutine '''
arg=''
p=multiprocessing.Process(target=job, args=(arg))
self.thrd_list.append( p )
p.start()
print("Start the job GUI: {} with PID: {}".format(str(job) ,self.thrd[-1].pid), file=sys.stdout )
def closeEvent(self, event):
''' subroutine to define what happen when closing the main frame'''
self.statusBar().showMessage('waiting for a respond')
reply = QMessageBox.question(self, 'Message',
"Are you sure to quit?", QMessageBox.Yes |
QMessageBox.No, QMessageBox.No)
if reply == QMessageBox.Yes:
event.accept()
else:
event.ignore()
# Main
if __name__ == '__main__':
# Create QApplication and Main Frame
Qapp = QApplication(sys.argv) # creation de lappli Qt
Qapp.setStyle("fusion") #
frame1 = Frame_main() #
sys.exit(Qapp.exec_()) #
EDIT:
I found several similar case, like :
Python: multiprocessing in pyqt application
but none of them helped. I think this might be linked to fact that my case is using function and attributes of the MainWindow class.
You are correct that this is due to the attempt to fork on a method within the GUI class. Unfortunately windows doesn't really have the ability to fork processes the way linux does, which is why your code works on linux and not on windows. The Python 3 documentation for the multiprocessing library has some useful information on the differences and what the default method for starting a new process is under different platforms.
To answer your question: because your target is a method associated with a GUI object, it has to send the object state to the new process but fails to pickle it because a PyQt GUI object is far too complex to pickle.
If you rewrite your code so that the target function (and args specified) are picklable, your code will work.