Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Example code appears to discard reference to the created task #98

Open
AlexanderWells-diamond opened this issue Nov 2, 2023 · 1 comment

Comments

@AlexanderWells-diamond
Copy link

The aiohttp_fetch.py example file makes use of the @asyncSlot() decorator to allow passing the on_btnFetch_clicked async function to the .connect method of a button.

Under the covers, @asyncSlot() is just creating a Task object using asyncio.ensure_future(). In the documentation for that function, it says "Save a reference to the result of this function, to avoid a task disappearing mid-execution.". It seems that the example code does not do this, so may be liable for garbage-collection issues if the now reference-less task is cleaned up unexpectedly.

Is there a preferred pattern to use for these decorators? Or can the decorator be modified to keep a reference to the Task that is created?

@phausamann
Copy link

phausamann commented May 14, 2024

I've run into this issue, which results asyncSlot callbacks being cancelled unexpectedly. My solution is to patch the asyncSlot function to use the workaround suggested by the create_task docs:

import asyncio
import functools
import inspect
import sys

from PyQt5.QtCore import pyqtSlot

background_tasks = set()


def asyncSlot(*args, **kwargs):
    """Make a Qt async slot run on asyncio loop.

    Patched version of qasync.asyncSlot that keeps references to tasks, 
    so that they don't get garbage collected.
    """

    def _error_handler(task):
        try:
            task.result()
        except Exception:
            sys.excepthook(*sys.exc_info())
        finally:
            background_tasks.discard(task)

    def outer_decorator(fn):
        @pyqtSlot(*args, **kwargs)
        @functools.wraps(fn)
        def wrapper(*args, **kwargs):
            # Qt ignores trailing args from a signal but python does
            # not so inspect the slot signature and if it's not
            # callable try removing args until it is.
            task = None
            while len(args):
                try:
                    inspect.signature(fn).bind(*args, **kwargs)
                except TypeError:
                    if len(args):
                        # Only convert args to a list if we need to pop()
                        args = list(args)
                        args.pop()
                        continue
                else:
                    task = asyncio.ensure_future(fn(*args, **kwargs))
                    task.add_done_callback(_error_handler)
                    background_tasks.add(task)
                    break
            if task is None:
                raise TypeError("asyncSlot was not callable from Signal. Potential signature mismatch.")
            return task

        return wrapper

    return outer_decorator

If this is an acceptable way of fixing this bug, I'd be happy to open a PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants