Python асинхронные обратные вызовы и генераторы - PullRequest
10 голосов
/ 27 ноября 2009

Я пытаюсь преобразовать синхронную библиотеку для использования внутренней структуры асинхронного ввода-вывода. У меня есть несколько методов, которые выглядят так:

def foo:
  ....
  sync_call_1()   # synchronous blocking call
  ....
  sync_call_2()   # synchronous blocking call
  ....
  return bar

Для каждой из синхронных функций (sync_call_*) я написал соответствующую асинхронную функцию, которая принимает обратный вызов. Э.Г.

def async_call_1(callback=none):
  # do the I/O
  callback()

Теперь вопрос новичка в python - какой самый простой способ перевести существующие методы для использования этих новых асинхронных методов? То есть метод foo() выше должен теперь быть:

def async_foo(callback):
  # Do the foo() stuff using async_call_*
  callback()

Один очевидный выбор - передать обратный вызов в каждый асинхронный метод, который эффективно «возобновляет» вызывающую функцию «foo», а затем вызвать глобальный обратный вызов в самом конце метода. Однако это делает код хрупким, уродливым, и мне нужно было бы добавлять новый обратный вызов для каждого вызова метода async_call_*.

Есть ли простой способ сделать это, используя идиому Python, например, генератор или сопрограмму?

Ответы [ 3 ]

10 голосов
/ 31 декабря 2009

ОБНОВЛЕНИЕ : возьмите это с крошкой соли, поскольку я не в курсе современных разработок по асинхронности Python, включая gevent и asyncio и don на самом деле не имеет серьезного опыта работы с асинхронным кодом.


Существует три распространенных подхода к асинхронному кодированию без потоков в Python:

  1. Обратные вызовы - уродливо, но выполнимо, Twisted делает это хорошо.

  2. Генераторы - хорошо, но требуют все ваш код, чтобы следовать стилю.

  3. Использование реализации Python с реальными тасклетами - Stackless (RIP) и greenlet .

К сожалению, в идеале вся программа должна использовать один стиль, иначе все станет сложнее. Если у вас все в порядке с вашей библиотекой, предоставляющей полностью синхронный интерфейс, вы, вероятно, в порядке, но если вы хотите, чтобы несколько обращений к вашей библиотеке работали параллельно, особенно параллельно с другим асинхронным кодом, тогда вам нужен общее событие «реактор», способное работать со всем кодом.

Так что, если у вас есть (или вы ожидаете, что пользователь будет иметь) другой асинхронный код в приложении, принятие той же модели, вероятно, будет разумным.

Если вы не хотите разбираться во всей этой неразберихе, подумайте об использовании старых плохих тем. Они тоже безобразны, но работают со всем остальным.

Если вы действительно хотите понять, как сопрограммы могут помочь вам - и как они могут усложнить вам, «Любопытный курс по сопрограммам и параллелизму» Дэвида Бизли - хороший материал.

Greenlets может быть действительно самым чистым способом, если вы можете использовать расширение. У меня нет никакого опыта с ними, поэтому не могу сказать много.

2 голосов
/ 31 декабря 2009

Вам нужно сделать функцию foo также асинхронной. Как насчет этого подхода?

@make_async
def foo(somearg, callback):
    # This function is now async. Expect a callback argument.
    ...

    # change 
    #       x = sync_call1(somearg, some_other_arg)
    # to the following:
    x = yield async_call1, somearg, some_other_arg
    ...

    # same transformation again
    y = yield async_call2, x
    ...

    # change
    #     return bar
    # to a callback call
    callback(bar)

И make_async можно определить так:

def make_async(f):
    """Decorator to convert sync function to async
    using the above mentioned transformations"""
    def g(*a, **kw):
        async_call(f(*a, **kw))
    return g

def async_call(it, value=None):
    # This function is the core of async transformation.

    try: 
        # send the current value to the iterator and
        # expect function to call and args to pass to it
        x = it.send(value)
    except StopIteration:
        return

    func = x[0]
    args = list(x[1:])

    # define callback and append it to args
    # (assuming that callback is always the last argument)

    callback = lambda new_value: async_call(it, new_value)
    args.append(callback)

    func(*args)

ВНИМАНИЕ: я не проверял это

2 голосов
/ 27 ноября 2009

Есть несколько способов для мультиплексирования задач. Мы не можем сказать, что лучше для вашего случая, не зная, что вы делаете. Вероятно, самый простой / универсальный способ - использовать потоки. Взгляните на этот вопрос для некоторых идей.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...