twisted:`defer.execute`和`threads.deferToThread`的区别
在Twisted中,defer.execute()
和threads.deferToThread()
有什么区别呢?这两个方法都需要传入相同的参数——一个函数和调用这个函数时需要的参数——然后都会返回一个“延迟对象”,这个对象会在函数执行完后返回结果。
不过,threads
版本明确说明它会在一个线程中运行。而如果defer
版本没有说明,那调用它有什么意义呢?在反应器中运行的代码是不能阻塞的,所以它调用的任何函数也必须是非阻塞的。这样的话,你完全可以直接用defer.succeed(f(*args, **kwargs))
来代替defer.execute(f, args, kwargs)
,结果是一样的。
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),然后你可以在这个延迟对象上添加回调和错误回调。回调会按照正常的链式调用来触发。听起来有点疯狂,但实际上,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