Reputation: 1629
Im trying to send a signal from a non-main thread in PyQt but i dont know what am doing wrong! And when i execute the program it fails with this error:
QObject::connect: Cannot queue arguments of type 'QTextCursor'
(Make sure 'QTextCursor' is registered using qRegisterMetaType().)
here is my code:
class Sender(QtCore.QThread):
def __init__(self,q):
super(Sender,self).__init__()
self.q=q
def run(self):
while True:
pass
try: line = q.get_nowait()
# or q.get(timeout=.1)
except Empty:
pass
else:
self.emit(QtCore.SIGNAL('tri()'))
class Workspace(QMainWindow, Ui_MainWindow):
""" This class is for managing the whole GUI `Workspace'.
Currently a Workspace is similar to a MainWindow
"""
def __init__(self):
try:
from Queue import Queue, Empty
except ImportError:
while True:
#from queue import Queue, Empty # python 3.x
print "error"
ON_POSIX = 'posix' in sys.builtin_module_names
def enqueue_output(out, queue):
for line in iter(out.readline, b''):
queue.put(line)
out.close()
p= Popen(["java -Xmx256m -jar bin/HelloWorld.jar"],cwd=r'/home/karen/sphinx4-1.0beta5-src/sphinx4-1.0beta5/',stdout=PIPE, shell=True, bufsize= 4024)
q = Queue()
t = threading.Thread(target=enqueue_output, args=(p.stdout, q))
t.daemon = True # thread dies with the program
t.start()
self.sender= Sender(q)
self.connect(self.sender, QtCore.SIGNAL('tri()'), self.__action_About)
self.sender.start()
I think that my way of send parameter to the thread is wrong...
I need to know how to send parameters to a thread, in my case i need to send q
to the worker thread.
Upvotes: 9
Views: 20976
Reputation: 15662
Quite new to PyQt5, but this appears to happen when you try to do a GUI operation from a thread which is not the "application thread". I put this in quotes because it appears to be a mistake to think that, even in a fairly simple PyQt5 app, QApplication.instance().thread()
will always return the same object.
The thing to do is to use the signal/slot mechanism to send any kind of data from a worker thread (a thread created in my case by extending QtCore.QRunnable
, one other pattern apparently being QtCore.QThread
and QtCore.QObject.moveToThread
, see here).
Then also include a check in all your slot methods which are likely to receive data from a non-"application thread". Example which logs messages visually during execution:
def append_message(self, message):
# this "instance" method is very useful!
app_thread = QtWidgets.QApplication.instance().thread()
curr_thread = QtCore.QThread.currentThread()
if app_thread != curr_thread:
raise Exception('attempt to call MainWindow.append_message from non-app thread')
ms_now = datetime.datetime.now().isoformat(sep=' ', timespec='milliseconds')
self.messages_text_box.insertPlainText(f'{ms_now}: {message}\n')
# scroll to bottom
self.messages_text_box.moveCursor(QtGui.QTextCursor.End)
It's all too easy to just call this inadvertently and directly from a non-"application thread".
Making such a mistake then raise an exception is good, because it gives you a stack trace showing the culprit call. Then change the call so that it instead sends a signal to the GUI class, the slot for which could be the method in the GUI class (here append_message
), or alternatively one which then in turn calls append_message
.
In my example I've included the "scroll to bottom" line above because it was only when I added that line that these "cannot queue" errors started happening. In other words, it is perfectly possible to get away with a certain amount of non-compliant handling (in this case adding some more text with each call) without any error being raised... and only later do you then run into difficulties. To prevent this, I suggest that EVERY method in a GUI class with GUI functionality should include such a check!
Enforcement of thread compliance using a method decorator
I now (2023) use this for most methods in my PyQt projects. Any failures show the calling stack (which in fact logger.exception(...)
doesn't do in this context...)
# ideally, make your life easier and properly configure a non-root logger
logger = logging.getLogger()
...
def thread_check(gui_thread: bool):
def pseudo_decorator(func):
if not callable(func):
# coding error: decorator has been applied to something which is not a function
raise Exception(f'func is non-callable type {type(func)}')
def gui_checker_inner_function(*args, **kwargs):
try:
func.stack_trace = None
if QtWidgets.QApplication == None:
return None
if QtWidgets.QApplication.instance() != None:
app_thread = QtWidgets.QApplication.instance().thread()
curr_thread = QtCore.QThread.currentThread()
# None means we don't check the GUI status of the thread
if gui_thread != None:
if (curr_thread == app_thread) != gui_thread:
# NB WRONG THREAD! QT likely to crash soon afterwards if this happens ...
raise Exception(f'method {func.__qualname__} should have been called in {"GUI thread" if gui_thread else "non-GUI thread"}')
def executing_func(*args, **kwargs):
func.stack_trace = ''.join(traceback.format_stack())
thread_check.stack_trace = ''.join(traceback.format_stack())
thread_check.func = func
return func(*args, **kwargs)
return executing_func(*args, **kwargs)
except BaseException as e:
msg = f'stack trace:\n{func.stack_trace}\n'
if logger == None:
print(msg, file=sys.stderr)
else:
logger.exception(msg)
# NB a KeyboardInterrupt on the DOS screen from which the app was launched, will not be "seen"
# until you put focus back on the app (from the DOS screen)... but when you do this will stop the app:
if isinstance(e, KeyboardInterrupt):
sys.exit()
raise e
return gui_checker_inner_function
return pseudo_decorator
Usage:
@thread_check(True)
def my_gui_method(self):
...
@thread_check(False)
def my_non_gui_method(self):
...
@thread_check(None)
def can_work_in_either_context(self):
...
NB it is wrong to suppose there is little point in the last of these 3, where such compliance is not in fact checked. Qt failures are notorious for exceptions not being caught because nothing is in fact checking for an exception being raised! This will catch all BaseException
s and log a nice calling stack trace. Otherwise you have to bracket the content of each method in try ... except
. NB however, it won't actually catch all Qt exceptions thrown/raised in the C++ code but it often provides a lot of guidance to baffling crashes.
Upvotes: 6
Reputation: 11
I would like to add the following notes to the @mike rodent's post which solved my problem (I'm using PyQt5):
QObject::connect: Cannot queue arguments of type 'QTextCursor'
message I needed to find the following locations and add some code:
class_attribute = pyqtSignal(str)
.self.class_attribute.connect(self.slot_name)
self.class_attribute.emit(str)
Upvotes: 0
Reputation: 9853
Make sure 'QTextCursor' is registered using qRegisterMetaType().
Did you try to use qRegisterMetaType
function?
The official manual says:
The class is used as a helper to marshall types in QVariant and in queued signals and slots connections. It associates a type name to a type so that it can be created and destructed dynamically at run-time. Declare new types with Q_DECLARE_METATYPE() to make them available to QVariant and other template-based functions. Call qRegisterMetaType() to make type available to non-template based functions, such as the queued signal and slot connections.
Upvotes: 0