скрученный: разница между `defer.execute` и `threads.deferToThread`

В чем разница между defer.execute() и threads.deferToThread() в скрученном виде? Оба принимают одни и те же аргументы — функцию и параметры для ее вызова — и возвращают отложенное выполнение, которое будет запущено с результатом вызова функции.

В версии threads явно указано, что она будет выполняться в потоке. Однако, если версия defer этого не делает, то какой смысл ее называть? Код, работающий в реакторе, никогда не должен блокироваться, поэтому любая вызываемая им функция не должна блокироваться. В этот момент вы могли бы просто сделать defer.succeed(f(*args, **kwargs)) вместо defer.execute(f, args, kwargs) с теми же результатами.


person Claudiu    schedule 10.09.2010    source источник


Ответы (1)


defer.execute действительно выполняет функцию блокирующим образом, в том же потоке, и вы правы в том, что defer.execute(f, args, kwargs) делает то же самое, что и defer.succeed(f(*args, **kwargs)), за исключением того, что defer.execute вернет обратный вызов, который вызвал ошибку, если функция f выдает исключение. Между тем, в вашем примере defer.succeed, если функция вызвала исключение, оно распространится наружу, что может быть нежелательно.

Для простоты понимания я просто вставлю сюда исходный код defer.execute:

def execute(callable, *args, **kw):
    """Create a deferred from a callable and arguments.

    Call the given function with the given arguments.  Return a deferred which
    has been fired with its callback as the result of that invocation or its
    errback with a Failure for the exception thrown.
    """
    try:
        result = callable(*args, **kw)
    except:
        return fail()
    else:
        return succeed(result)

Другими словами, defer.execute — это просто ярлык для получения результата блокирующей функции в качестве отложенного, к которому вы затем можете добавить обратные вызовы/ошибки. Обратные вызовы будут запущены с обычной семантикой цепочки. Это кажется немного сумасшедшим, но Deferred'ы могут «срабатывать» до того, как вы добавите обратные вызовы, и обратные вызовы все равно будут вызываться.


Итак, отвечая на ваш вопрос, почему это полезно? Что ж, defer.execute полезен как для тестирования/мока, так и для простой интеграции асинхронного API с синхронным кодом.

Также полезен defer.maybeDeferred, который вызывает функцию, а затем, если функция уже возвращает deferred, просто возвращает его, в противном случае функции аналогичны defer.execute. Это полезно, когда вы пишете API, который ожидает вызываемый объект, который при вызове дает вам отсрочку, и вы также хотите иметь возможность принимать обычные блокирующие функции.

Например, предположим, что у вас есть приложение, которое извлекает страницы и что-то с ними делает. И по какой-то причине вам нужно было запускать это синхронно для конкретного варианта использования, например, в однократном сценарии crontab или в ответ на запрос в приложении WSGI, но при этом сохранять ту же кодовую базу. Если бы ваш код выглядел так, это можно было бы сделать:

from twisted.internet import defer
from twisted.web.client import getPage

def process_feed(url, getter=getPage):
    d = defer.maybeDeferred(getter, url)
    d.addCallback(_process_feed)

def _process_feed(result):
    pass # do something with result here

Чтобы запустить это в синхронном контексте, без реактора, вы можете просто передать альтернативную функцию получения, например:

from urllib2 import urlopen

def synchronous_getter(url):
    resp = urlopen(url)
    result = resp.read()
    resp.close()
    return result
person Crast    schedule 10.09.2010