Package: qt-async-threads
qt-async-threadsallows Qt applications to use convenientasync/awaitsyntax to run
computational intensive or IO operations in threads, selectively changing the code slightly
to provide a more responsive UI.The objective of this library is to provide a simple and convenient way to improve
UI responsiveness in existing Qt applications by usingasync/await, while
at the same time not requiring large scale refactorings.SupportsPyQt5,PyQt6,PySide2, andPySide6thanks toqtpy.ExampleThe widget below downloads pictures of cats when the user clicks on a button (some parts omitted for brevity):classCatsWidget(QWidget):def__init__(self,parent:QWidget)->None:...self.download_button.clicked.connect(self._on_download_button_clicked)def_on_download_button_clicked(self,checked:bool=False)->None:self.progress_label.setText("Searching...")api_url="https://api.thecatapi.com/v1/images/search"foriinrange(10):try:# Search.search_response=requests.get(api_url)self.progress_label.setText("Found, downloading...")# Download.url=search_response.json()[0]["url"]download_response=requests.get(url)exceptConnectionErrorase:QMessageBox.critical(self,"Error",f"Error:{e}")returnself._save_image_file(download_response)self.progress_label.setText(f"Done downloading image{i}.")self.progress_label.setText(f"Done,{downloaded_count}cats downloaded")This works well, but while the pictures are being downloaded the UI will freeze a bit,
becoming unresponsive.Withqt-async-threads, we can easily change the code to:classCatsWidget(QWidget):def__init__(self,runner:QtAsyncRunner,parent:QWidget)->None:...# QtAsyncRunner allows us to submit code to threads, and# provide a way to connect async functions to Qt slots.self.runner=runner# `to_sync` returns a slot that Qt's signals can call, but will# allow it to asynchronously run code in threads.self.download_button.clicked.connect(self.runner.to_sync(self._on_download_button_clicked))asyncdef_on_download_button_clicked(self,checked:bool=False)->None:self.progress_label.setText("Searching...")api_url="https://api.thecatapi.com/v1/images/search"foriinrange(10):try:# Search.# `self.runner.run` calls requests.get() in a thread,# but without blocking the main event loop.search_response=awaitself.runner.run(requests.get,api_url)self.progress_label.setText("Found, downloading...")# Download.url=search_response.json()[0]["url"]download_response=awaitself.runner.run(requests.get,url)exceptConnectionErrorase:QMessageBox.critical(self,"Error",f"Error:{e}")returnself._save_image_file(download_response)self.progress_label.setText(f"Done downloading image{i}.")self.progress_label.setText(f"Done,{downloaded_count}cats downloaded")By using aQtAsyncRunnerinstance and changing the slot to anasyncfunction, therunner.runcalls
will run the requests in a thread, without blocking the Qt event loop, making the UI snappy and responsive.Thanks to theasync/awaitsyntax, we can keep the entire flow in the same function as before,
including handling exceptions naturally.We could rewrite the first example using aThreadPoolExecutororQThreads,
but that would require a significant rewrite.DocumentationFor full documentation, please seehttps://qt-async-threads.readthedocs.io/en/latest.Differences with other librariesThere are excellent libraries that allow to use async frameworks with Qt:qasyncintegrates withasyncioqtriointegrates withtrioThose libraries fully integrate with their respective frameworks, allowing the application to asynchronously communicate
with sockets, threads, file system, tasks, cancellation systems, use other async libraries
(such ashttpx), etc.They are very powerful in their own right, however they have one downside in that they require yourmainentry point to also beasync, which might be hard to accommodate in an existing application.qt-async-threads, on the other hand, focuses only on one feature: allow the user to leverageasync/awaitsyntax tohandle threads more naturally, without the need for major refactorings in existing applications.LicenseDistributed under the terms of theMITlicense.