pythonqt icon indicating copy to clipboard operation
pythonqt copied to clipboard

Queued slot calls

Open githubuser0xFFFF opened this issue 3 years ago • 1 comments

The merge request is a proposal for the implementation of queued slot calls.

In our application we run the Python interpreter in a worker thread and not in the UI main thread. That means, with the current implementation it is not possible/allowed to call slots of UI objects / widgets from our python scripts.

We changed the PythonQtSlotInfo::invokeQtMethod to check if the given object thread is the current thread. If the threads are equal, then we use the default implementation. If the slots are differents (Python interpreter in worker thread and Widget in main UI thread), then we call the slot via a blocking queued connection - like it is done from Qt in the QMetaMehod::invoke implementation.

In the proposal we provided two different implementations. The first one posts a QMetaCallEvent like the QMetaMehod::invoke implementation. The advantages are, that the slot is called in the given object thread and that it has less overhead then the second implementation. The disadvandtage is, that is uses private API - the current implementation may only compile with Qt 5.15.

The second implementation uses a PythonQtMetaCallDelegate object that is created in the UI main thread. That means, the slot call is delegated to the UI main thread via invocation of a PythonQtMetaCallDelegate slot. This implementation does not use private API but the slots are always called in the UI main thread and not in the thread of the given object.

githubuser0xFFFF avatar Nov 12 '21 08:11 githubuser0xFFFF

I updated the merge request. I decided to use the PythonQtMetaCallDelegate implementation to get rid of private API dependency. I also implemented proper exception handling for delegated meta calls.

With this implementation it is possible to run the python interpreter in a worker thread and call slots of widgets in the main UI thread.

githubuser0xFFFF avatar Nov 16 '21 13:11 githubuser0xFFFF

No, this will not do. This code assumes that every Qt object lives in the main thread, which is an invalid assumption. One can start new Qt threads with their own event loops from Python (we do this in MeVisLab to handle asyncio tasks).

I also don't like that this will do cross-thread communication implicitly.

Personally I would prefer an approach where the QMetaObject wrapper gets a static invokeMethod method that takes the target object, the method name, connection style, and a list of arguments (almost like in C++, but more user-friendly). Of course this would mean figuring out which slot to use specifically (in case of overloads), but this is not much different from what is done already. Depending on whether Qt.QueuedConnection or Qt.BlockedQueuedConnection is used one would get a return value or not - if no return value is required this can be infinitely faster if the receiving thread is not idle in its event loop at the moment.

usiems avatar Jan 04 '23 17:01 usiems

Thank you for your feedback. I'm closing this pull request.

githubuser0xFFFF avatar Mar 07 '24 08:03 githubuser0xFFFF