Im trying to send parameter to a Qthread that is not the main thread.
i have this object in my main thread and i want to send it to another thread:
q = Queue()
I want so send q to this thread:
class Sender(QtCore.QThread):
def __init__(self,q):
super(Sender,self).__init__()
self.q=q
def run(self):
while True:
try: line = q.get_nowait()
# or q.get(timeout=.1)
except Empty:
pass
else:
self.emit(QtCore.SIGNAL('tri()'))
Im trying this:
class Sender(QtCore.QThread):
def __init__(self,q):
super(Sender,self).__init__()
self.q=q
self.sender= Sender(q)
But im getting this error:
QObject::connect: Cannot queue arguments of type 'QTextCursor'
(Make sure 'QTextCursor' is registered using qRegisterMetaType().)
How can i do this?
Please help!
There is no problem with your QThread subclass, and how you are setting it up to pass a Queue object. Though I do also recommend passing and setting an optional parent as the second parameter.
What you are mostly likely running into is you are passing objects that perform QtGui operations (drawing related) into your thread. If that is the case, you cannot call any QtGui drawing related methods. They all must be performed in the main thread. Do any data processing in other threads and then emit signals for the main thread to do widget updates.
Look for what you are sending into the queue, and specifically what you are doing with it inside of your thread, as a cue to the source of the error. Somewhere, you are doing something with a QTextCursor, which is trying to trigger and queue up a paint event.
Related
This class suppose to handle a long task in a separate thread.
Despite moving my object to a new thread, and using queued connection, the signal is handled at the main gui thread instead of self.thread. The main gui thread is the thread that did called startit of course..
This is the code:
class DoLongProcess(QObject):
def __init__(self,task):
QObject.__init__(self)
self._task=task
self._realtask=None
self.thread = QThread()
self.moveToThread(self.thread)
self.thread.started.connect(self.run,Qt.QueuedConnection)
#Slot()
def run(self):
self._realtask()
def startit(self,*params):
self._realtask= partial(self._task,*params)
self.thread.start()
I use pyside6. I tried to to split __init__ into two methods and moving the thread from outside. Also tried without the QueuedConnection attribute (which suppose to do this exactly). None of this had an effect.
Do you know why it doesn't work as expected?
This question already has answers here:
Updating GUI elements in MultiThreaded PyQT
(2 answers)
Closed 1 year ago.
I am creating a Server-Client GUI application in Python using PyQt5. Whenever a new clients is connected to server I need to display the client details at server side. I am using a separate thread for sockets. Whenever I call the client_connect function to add the widget at server side I get error due to which widget is not displayed. I think since the GUI and socket codes are in different threads due to this I am getting the error.
QObject::setParent: Cannot set parent, new parent is in a different thread
QObject::installEventFilter(): Cannot filter events for objects in a different thread.
QBasicTimer::start: QBasicTimer can only be used with threads started with QThread
QBasicTimer::start: QBasicTimer can only be used with threads started with QThread
QBasicTimer::start: QBasicTimer can only be used with threads started with QThread
QObject::startTimer: Timers can only be used with threads started with QThread
QBasicTimer::start: QBasicTimer can only be used with threads started with QThread
Main Function
if __name__ == "__main__":
thread1 = threading.Thread(target = ui.server_socket)
thread1.start()
client_connect Function - I have created a separate file for the widget and I am inserting the widget in tableWidget. It works if i directly call the function but if i call it from the socket code it gives me error.
def client_connect(self,clientid):
self.clientCount = self.clientCount + 1
self.clientList.append(clientid)
self.clientDict[clientid] = QtWidgets.QWidget()
self.clientDict[clientid].ui = clients()
self.clientDict[clientid].ui.setupUi(self.clientDict[clientid])
self.clientDict[clientid].ui.label_clientid.setText(str(clientid))
self.tableWidget_client.setRowCount(self.clientCount)
self.tableWidget_client.setCellWidget(self.clientCount-1,0,self.clientDict[clientid])
Socket programming
def start_socket(self):
self.ssock.listen(20)
while True:
conn, c_addr = self.ssock.accept()
print(conn,c_addr)
thread2 = threading.Thread(target=self.handle_client, args=(conn,c_addr))
thread2.start()
def handle_client(self, conn, c_addr):
try:
self.c = conn.recv(1024).decode(self.FORMAT)
thread3 = threading.Thread(target = self.client_connect, args = (self.c_id,))
thread3.start()
except socket.error as e:
print(e)
As you suspected, you shouldn't directly modify PyQt objects from different threads. You may read the docs about threads and objects.
Docs specifically mention that:
If you are calling a function on an QObject subclass that doesn't live in the current thread and the object might receive events, you must protect all access to your QObject subclass's internal data with a mutex; otherwise, you may experience crashes or other undesired behavior. [.......] On the other hand, you can safely emit signals from your QThread::run() implementation, because signal emission is thread-safe.
The way PyQt wants you to do it is to use Signals and Slots. Below is a small example of emitting a signal from worker's run function.
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
# Some Ui stuff is happening here
self.worker = Worker(self.startParm)
self.worker.client_connected.connect(self.display_client_info)
self.worker.start() # When you want to process a new client connection
def display_client_info(self, client_id):
# Code here should be similar to what you're trying to do in client_connect
class Worker(QThread):
def __init__(self):
super(Worker, self).__init__()
self.client_connected = pyqtSignal(int)
def run(self, clientid):
# The code you want to be executed in a thread
# For example whatever happens in handle_client, except in your design you're going
# through a middle thread and this is a basic example.
# Eventually emit client_connected signal with the client id:
self.client_connected.emit(client_id)
The main concepts to take from this:
Only modify PyQt objects from the main thread
Create signals and connect them to functions that will update UI for you in the main thread
Emit signals from within threads when you need to
Overriding QThread's run function is a more simple but can be limited way of using this. You may look into creating QObjects and using moveToThread function for more options.
I have a PySide2 application that runs mostly well. However, from time to time, it exits itself without any error (stdout, stderr are empty, even when run from terminal - tried everything) while a worker thread sends updates to the GUI thread via signals.
To emit these signals however, I'm doing it in a way such that my libraries can just take callbacks and not even know they are actually emitting signals, and I was wondering whether this could be a potential source of crashes:
Gui code:
from mylib import do_threaded_work
MyClass(QObject):
sig = Signal(int)
# ... proper initialization and connection of self.sig to a QProgressBar setValue slot
def on_some_button_pressed(self):
threading.Thread(target=do_threaded_work, args=(self.sig.emit,), daemon=True).start()
mylib.py dummy code:
do_threaded_work(on_progress):
i = 0
while (True):
# ... do some work
on_progress(i)
i = min(100, i + 1)
As you see, I'm directly passing the emit function of my signal instance to the library, that calls it and thus should emit the signal. Is it OK to do that?
Sometimes, I pass the signal object self.sig as argument instead of self.sig.emit, then call argument.emit(...) in the library code. I assume it has the same effect?
The reason I ask is because I didn't find any counter argument stating not to do this in PySide2, and the official documentation on signal and slots is quite light here.
You guys have any input on this? Thanks!
I have a Tkinter wrapper written over robocopy.exe, the code is organized as shown below :-
Tkinter Wrapper :
Spawns a new thread and pass the arguments, which includes source/destination and other parameters
(Note : Queue object is also passed to thread, since the thread will read the output from robocopy and will put in queue, main tkinter thread will keep on polling queue and will update Tkinter text widget with the output)
Code Snippet
... Code to poll queue and update tk widget ...
q = Queue.Queue()
t1 = threading.Thread(target=CopyFiles,args=(q,src,dst,), kwargs={"ignore":ignore_list})
t1.daemon = True
t1.start()
Thread : (In a separate file)
Below is the code snippet from thread
def CopyFiles(q,src,dst,ignore=None):
extra_args = ['/MT:15', '/E', '/LOG:./log.txt', '/tee', '/r:2', '/w:2']
if len(ignore) > 0:
extra_args.append('/xf')
extra_args.extend(ignore)
extra_args.append('/xd')
extra_args.extend(ignore)
command_to_pass = ["robocopy",src, dst]
command_to_pass.extend(extra_args)
proc = subprocess.Popen(command_to_pass,stdout=subprocess.PIPE)
while True:
line = proc.stdout.readline()
if line == '':
break
q.put(line.strip())
Code which is called, when tkinter application is closed :-
def onQuit(self):
global t1
if t1.isAlive():
pass
if tkMessageBox.askyesno("Title", "Do you really want to exit?"):
self.destroy()
self.master.destroy()
Problem
Whenever, I close the tkinter application when robocopy is running, python application closes but the robocopy.exe keeps on running.
I have tried setting the thread as daemon, but it has no effect. How can I stop robocopy.exe when onQuit method is called?
To simplify things let's ignore Tkinter and the fact that a separate thread is used.
The situation is that your app spawns a subprocess to execute an external program (robocopy.exe in this question), and you'd need to stop the spawned program from you're application on a certain event (when the Tkinter app is closing in this question).
This requires an inter process communication mechanism, so the spawned process would be notified of the event, and reacts accordingly. A common mechanism is to use signals provided by the OS.
You could send a signal (SIGTERM) to the external process and ask from it to quit. Assuming that the program reacts to signal as expected (most well written applications do) you'll get the desired behavior (the process will terminate).
Using the terminate method on the subprocess will send the proper signal of current platform to the subprocess.
You'd need a reference to the subprocess object proc in the onQuit function (from the provided code I see onQuit is a function and not an object method, so it could use a global variable to access proc), so you can call the terminate method of the process:
def onQuit(self):
global t1, proc
if t1.isAlive():
pass
# by the way I'm not sure about the logic, but I guess this
# below statement should be an elif instead of if
if tkMessageBox.askyesno("Title", "Do you really want to exit?"):
proc.terminate()
self.destroy()
self.master.destroy()
This code assumes you're storing the reference to the subprocess in the global scope, so you'd have to modify the CopyFiles as well.
I'm not sure how robocopy handles terminate signals and I'm guessing that's not something that we have any control over.
If you had more control on the external program (could modify the source), there might have been more options, for example sending messages using stdio, or using a shared memory, etc.
I have a program with a GUI that needs to do some multiprocessing. The point of it is to avoid freezing the GUI, and to allow the user using some other buttons while the process is running.
I'd like then to define a method like the following:
def start_waiting(self, parent, func, args):
self.window_waiting.show()
task=Process(func(*args))
task.start()
task.join()
# Some code to execute at the end of the process
#
#...
The problem is that join() is not working, and I need it because the code executed after join() indicates when the Process has ended. I'll use that code to update a cancel button of the window_waiting.
I then came in mind with another solution to avoid usingjoin(), I replaced it with:
while task.is_alive():
time.sleep(0.5)
But it didn't worked neither, so I tried a plan C, which was to create a queue:
def worker(input, output):
for func, args in iter(input.get, 'STOP'):
result=func(*args)
output.put(result)
task_queue = Queue()
done_queue = Queue()
task_queue.put(task)
Process(target=worker, args=(task_queue, done_queue)).start()
done_queue.get()
The last code gave me the error: 'Pickling an AuthenticationString object is '
TypeError: Pickling an AuthenticationString object is disallowed for security reasons
Which lead me to multiprocessing.Process subclass using shared queue but I haven't managed to solve the problem :/
Your first example should look like this:
def start_waiting(self,parent,func,args):
...not relevant code..
self.window_waiting.show()
task=Process(target=func, args=args) # This line is different.
task.start()
task.join()
The way you had it, it wasn't actually executing func in a child process; it was executing it in the parent, and then passing the return value to Process. When you called task.start(), it would probably instantly fail, because you were passing it whatever func returned, rather than a function object.
Note that because you're calling task.join() inside start_waiting, it's probably going to block your GUI, because start_waiting won't return until func completes, even though its running in a child process. The only way it wouldn't block is if you're running start_waiting in a separate thread from the GUI's event loop. You probably want something more like this:
def start_waiting(self,parent,func,args):
...not relevant code..
self.window_waiting.show() # You should interact with the GUI in the main thread only.
self.task = Process(target=func, args=args) # This line is different.
self.thrd = threading.Thread(target=self.start_and_wait_for_task)
self.thrd.start()
def start_and_wait_for_task(self):
""" This runs in its own thread, so it won't block the GUI. """
self.task.start()
self.task.join()
# If you need to update the GUI at all here, use GLib.idle_add (see https://wiki.gnome.org/Projects/PyGObject/Threading)
# This is required to safely update the GUI from outside the main thread.