Skip to main content

Use convenient async/await syntax to spawn threads in Qt applications

Project description

https://img.shields.io/pypi/v/qt-async-threads.svg https://img.shields.io/conda/vn/conda-forge/qt-async-threads.svg https://img.shields.io/pypi/pyversions/qt-async-threads.svg https://github.com/nicoddemus/qt-async-threads/workflows/test/badge.svg pre-commit.ci status https://img.shields.io/badge/code%20style-black-000000.svg https://readthedocs.org/projects/qt-async-threads/badge/?version=latest

qt-async-threads allows Qt applications to use convenient async/await syntax 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 using async/await, without having to do large scale refactorings to do so.

Example

The widget below downloads pictures of cats when the user clicks on a button (some parts omitted for brevity):

class CatsWidget(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"

        for i in range(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)
            except ConnectionError as e:
                QMessageBox.critical(self, "Error", f"Error: {e}")
                return

            self._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.

With qt-async-threads, we can easily change the code to:

class CatsWidget(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)
        )

    async def _on_download_button_clicked(self, checked: bool = False) -> None:
        self.progress_label.setText("Searching...")

        api_url = "https://api.thecatapi.com/v1/images/search"

        for i in range(10):
            try:
                # Search.
                # `self.runner.run` calls requests.get() in a thread,
                # but without blocking the main event loop.
                search_response = await self.runner.run(requests.get, api_url)
                self.progress_label.setText("Found, downloading...")

                # Download.
                url = search_response.json()[0]["url"]
                download_response = await self.runner.run(requests.get, url)
            except ConnectionError as e:
                QMessageBox.critical(self, "Error", f"Error: {e}")
                return

            self._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 a QtAsyncRunner instance and changing the slot to an async function, the runner.run calls will run the requests in a thread, without blocking the Qt event loop, making the UI snappy and responsive.

Thanks to the async/await syntax, we can keep the entire flow in the same function as before, including handling exceptions naturally.

We could rewrite the first example using a ThreadPoolExecutor or QThreads, but that would require a significant rewrite of the flow of the code if we don’t want to block the Qt event loop.

Documentation

For full documentation, please see https://qt-async-threads.readthedocs.io/en/latest.

Differences with other libraries

There are excellent libraries that allow to use async frameworks with Qt:

Those 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 as httpx), etc.

They are very powerful in their own right, however they have one downside in that they require your main entry point to also be async, 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 leverage async/await syntax to handle threads more naturally, without the need for major refactorings in existing applications.

License

Distributed under the terms of the MIT license.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

qt-async-threads-0.1.tar.gz (31.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

qt_async_threads-0.1-py3-none-any.whl (12.7 kB view details)

Uploaded Python 3

File details

Details for the file qt-async-threads-0.1.tar.gz.

File metadata

  • Download URL: qt-async-threads-0.1.tar.gz
  • Upload date:
  • Size: 31.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.0 CPython/3.10.2

File hashes

Hashes for qt-async-threads-0.1.tar.gz
Algorithm Hash digest
SHA256 106a2f5faf9146a81646544650e20380bebb0160e9c8455e4ea0e9f6f7335ac1
MD5 0fc370c6415594e407ce7f54fd57302e
BLAKE2b-256 300599770c229d5cf0f9c5c9dc37b7e09a98339ed9eace4760d472df6ff8f74d

See more details on using hashes here.

File details

Details for the file qt_async_threads-0.1-py3-none-any.whl.

File metadata

  • Download URL: qt_async_threads-0.1-py3-none-any.whl
  • Upload date:
  • Size: 12.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.0 CPython/3.10.2

File hashes

Hashes for qt_async_threads-0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 199b91cb0b8965ecf0073ede1e59614bd636e2d5a8f057047e278a25f89487ea
MD5 0ef1a502bc70de802554fe9ec8a26d39
BLAKE2b-256 90e93e4081f58e816d97ea9e33c1698c2c954a3e220512ca52d662dc8171d4ca

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page