twisted:`defer.execute`和`threads.deferToThread`的区别

8 投票
1 回答
3115 浏览
提问于 2025-04-16 03:58

在Twisted中,defer.execute()threads.deferToThread()有什么区别呢?这两个方法都需要传入相同的参数——一个函数和调用这个函数时需要的参数——然后都会返回一个“延迟对象”,这个对象会在函数执行完后返回结果。

不过,threads版本明确说明它会在一个线程中运行。而如果defer版本没有说明,那调用它有什么意义呢?在反应器中运行的代码是不能阻塞的,所以它调用的任何函数也必须是非阻塞的。这样的话,你完全可以直接用defer.succeed(f(*args, **kwargs))来代替defer.execute(f, args, kwargs),结果是一样的。

1 个回答

9

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),然后你可以在这个延迟对象上添加回调和错误回调。回调会按照正常的链式调用来触发。听起来有点疯狂,但实际上,Deferreds 可以在你添加回调之前就“触发”,而这些回调仍然会被调用。


那么,回答你的问题,这有什么用呢? 其实,defer.execute 对于测试/模拟以及将异步 API 与同步代码结合使用都很有用。

另外,defer.maybeDeferred 也很有用,它会调用函数,如果这个函数已经返回了一个延迟对象,就直接返回它;否则,它的功能类似于 defer.execute。这在你编写一个 API 时特别有用,API 期望一个可调用的对象,当调用它时返回一个延迟对象,同时你也想接受普通的阻塞函数。

举个例子,假设你有一个应用程序,它会获取网页并对其进行处理。出于某种原因,你需要在特定的情况下以同步的方式运行这个程序,比如在一个单次执行的定时任务脚本中,或者在响应 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

撰写回答