Создание временного асинхронного обратного вызова таймера для связанного метода с python-asyncio

Я пытаюсь создать своего рода обратный вызов таймера для привязанного метода async, используя asyncio цикл обработки событий. Теперь проблема в том, что связанный асинхронный метод не должен содержать сильную ссылку на экземпляр, иначе последний никогда не будет удален. Таймер обратного вызова должен жить только до тех пор, пока родительский экземпляр. Я нашел решение, но я не думаю, что это красиво:

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

Есть ли лучший, более питонский способ сделать это? Таймер обратного вызова действительно должен быть асинхронным. Без asyncio, weakref.WeakMethod, вероятно, будет путь Но asyncio.ensure_future требует объект сопрограммы, поэтому он не будет работать в этом случае.

7 голосов | спросил pumphaus 19 +03002015-10-19T17:46:49+03:00312015bEurope/MoscowMon, 19 Oct 2015 17:46:49 +0300 2015, 17:46:49

4 ответа


0

Основываясь на ответах Хуазуо Гао и Джермена, я реализовал 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())
ответил pumphaus 20 +03002015-10-20T11:22:31+03:00312015bEurope/MoscowTue, 20 Oct 2015 11:22:31 +0300 2015, 11:22:31
0

Несколько месяцев назад я столкнулся с почти такой же проблемой и написал для этого декоратор:

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

Ваш код может быть переписан естественным образом:

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

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

    @weakmethod
    async def update(self):
        while True:
            await asyncio.sleep(1)
            print("IN update of object ", self)

Важно:

  • weakref.proxy не мешает вам получить надежную ссылку. Кроме того, не гарантируется, что он будет вести себя точно так же, как исходный объект.

  • Моя реализация не охватывает все возможности.

ответил Huazuo Gao 19 +03002015-10-19T19:39:17+03:00312015bEurope/MoscowMon, 19 Oct 2015 19:39:17 +0300 2015, 19:39:17
0

Извините, я не уверен, правильно ли я понял ваш вопрос. Это решение, которое вы ищете?

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

Вывод:

IN update of object <__main__.ClassWithTimer object at 0x000000468D0FB208>
IN update of object <__main__.ClassWithTimer object at 0x000000468D0FB208>
IN update of object <__main__.ClassWithTimer object at 0x000000468D0FB208>
IN update of object <__main__.ClassWithTimer object at 0x000000468D0FB208>
deleted ClassWithTimer!
[Finished in 5.2s]

Еще один способ без контекстного менеджера:

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())
ответил Mikhail Gerasimov 19 +03002015-10-19T19:17:33+03:00312015bEurope/MoscowMon, 19 Oct 2015 19:17:33 +0300 2015, 19:17:33
0
  1. Python 3.5 очень хорошо обрабатывает ссылки на циклы (см. PEP 442 https: //www.python.org/dev/peps/pep-0442/)

  2. Я скоро запишу тайм-аут асинхронного диспетчера контекста в asyncio, см. черновик:

`

import asyncio
import warnings


class Timeout:
    def __init__(self, timeout, *, raise_error=False, loop=None):
        self._timeout = timeout
        if loop is None:
            loop = asyncio.get_event_loop()
        self._loop = loop
        self._raise_error = raise_error
        self._task = None
        self._cancelled = False
        self._cancel_handler = None

    async def __aenter__(self):
        self._task = asyncio.Task.current_task(loop=loop)
        self._cancel_handler = self._loop.call_later(
            self._cancel, self._timeout)

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        if self._cancelled:
            if self._raise_error:
                raise asyncio.TimeoutError
            else:
                # suppress
                return True
        else:
            self._cancel_handler.cancel()
        # raise all other errors

    def __del__(self):
        if self._task:
            # just for preventing improper usage
            warnings.warn("Use async with")

    def _cancel(self):
        self._cancelled = self._task.cancel()


async def long_running_task():
    while True:
        asyncio.sleep(5)


async def run():
    async with Timeout(1):
        await long_running_task()


loop = asyncio.get_event_loop()
loop.run_until_complete(run())
ответил Andrew Svetlov 19 +03002015-10-19T21:56:10+03:00312015bEurope/MoscowMon, 19 Oct 2015 21:56:10 +0300 2015, 21:56:10

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132