使用pythonasynci创建绑定方法的临时异步计时器回调

2024-10-01 13:38:08 发布

您现在位置:Python中文网/ 问答频道 /正文

我试图使用asyncio的事件循环创建一种对绑定async方法的计时器回调。现在的问题是绑定的异步方法不应该包含对实例的强引用,否则后者将永远不会被删除。计时器回调应该只和父实例一样长。 我找到了一个解决办法,但我觉得不太好:

import asyncio
import functools
import weakref

class ClassWithTimer:
    def __init__(self):
        asyncio.ensure_future(
            functools.partial(
                ClassWithTimer.update, weakref.ref(self)
            )()
        )

    def __del__(self):
        print("deleted ClassWithTimer!")

    async def update(self):
        while True:
            await asyncio.sleep(1)
            if self() is None: break
            print("IN update of object " + repr(self()))

async def run():
    foo = ClassWithTimer()
    await asyncio.sleep(5)
    del foo

loop = asyncio.get_event_loop()
loop.run_until_complete(run())

有没有更好的,更像Python的方法来做这件事?计时器回调确实需要异步。 如果没有asyncioweakref.WeakMethod很可能是一条路。但是asyncio.ensure_future需要一个协程对象,所以在这种情况下它不起作用。在


Tags: 实例方法runimportselfloopasyncioasync
3条回答

根据华左高和格尔曼的回答,我实现了ensure_weakly_binding_future,它与ensure_future基本相同,但没有对绑定方法的实例进行强有力的引用。它不会修改整个绑定(就像基于修饰符的解决方案那样),并在父实例被删除时正确取消未来:

import asyncio
import weakref

def ensure_weakly_binding_future(method):
    class Canceller:
        def __call__(self, proxy):
            self.future.cancel()

    canceller = Canceller()
    proxy_object = weakref.proxy(method.__self__, canceller)
    weakly_bound_method = method.__func__.__get__(proxy_object)
    future = asyncio.ensure_future(weakly_bound_method())
    canceller.future = future

class ClassWithTimer:
    def __init__(self):
        ensure_weakly_binding_future(self.update)

    def __del__(self):
        print("deleted ClassWithTimer!", flush=True)

    async def update(self):
        while True:
            await asyncio.sleep(1)
            print("IN update of object " + repr(self), flush=True)

async def run():
    foo = ClassWithTimer()
    await asyncio.sleep(5.5)
    del foo
    await asyncio.sleep(2.5)

loop = asyncio.get_event_loop()
loop.run_until_complete(run())

对不起,我不知道我是否理解你的问题。这是你要找的解决方案吗?在

import asyncio


class ClassWithTimer:
    async def __aenter__(self):
        self.task = asyncio.ensure_future(self.update())

    async def __aexit__(self, *args):
        try:
            self.task.cancel()  # Just cancel updating when we don't need it.
            await self.task
        except asyncio.CancelledError:  # Ignore CancelledError rised by cancelled task.
            pass
        del self  # I think you don't need this in real life: instance should be normally deleted by GC.

    def __del__(self):
        print("deleted ClassWithTimer!")

    async def update(self):
        while True:
            await asyncio.sleep(1)
            print("IN update of object " + repr(self))


async def run():
    async with ClassWithTimer():  # Use context manager to handle when we need updating.
        await asyncio.sleep(5)


loop = asyncio.get_event_loop()
loop.run_until_complete(run())

输出:

^{pr2}$

另一种没有上下文管理器的方法:

import asyncio


class ClassWithTimer:
    def __init__(self):
        self.task = asyncio.ensure_future(self.update())

    async def release(self):
        try:
            self.task.cancel()
            await self.task
        except asyncio.CancelledError:
            pass
        del self

    def __del__(self):
        print("deleted ClassWithTimer!")

    async def update(self):
        while True:
            await asyncio.sleep(1)
            print("IN update of object " + repr(self))


async def run():
    foo = ClassWithTimer()
    await asyncio.sleep(5)
    await foo.release()


loop = asyncio.get_event_loop()
loop.run_until_complete(run())

几个月前,我遇到了几乎相同的问题,我写了一个装饰师来解决这个问题:

def weakmethod(f):
    @property
    def get(self):
        return f.__get__(weakref.proxy(self))
#       self = weakref.proxy(self)
#       if hasattr(f, '__get__'):
#            raise RuntimeWarning(
#                'weakref may not work unless you implement '
#                'the property protocol carefully by youself!'
#            )
#           return f.__get__(self)
#       if asyncio.iscoroutinefunction(f):
#           #Make the returned method a coroutine function, optional
#           async def g(*arg, **kwarg):
#               return await f(self, *arg, **kwarg)
#       else:
#           def g(*arg, **kwarg):
#               return f(self, *arg, **kwarg)
#       return g
#       #Still some situations not taken into account?
    return get

然后,您的代码可以以非常自然的方式重写:

^{pr2}$

重要提示:

  • weakref.proxy不会阻止您获取强引用。此外,不能保证它的行为与原始对象完全相同。

  • 我的实施并没有涵盖所有可能性。

相关问题 更多 >