重用Twisted中的延迟对象 [英] Re-using deferred objects in Twisted

查看:75
本文介绍了重用Twisted中的延迟对象的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在Twisted中,似乎延迟的对象在其回调触发后只能使用一次,这与我使用过的其他基于承诺的库相反:

 来自twisted.internet导入延迟


类Foo(对象):

def __init __(self) :
self.dfd = defer.Deferred()

def async(self):
return self.dfd

@ defer.inlineCallbacks
def func(self):
打印'Started!'

结果= yield self.async()

打印'结果停止:{0} '.format(结果)

如果__name__ =='__main__':
foo = Foo()
foo.func()

导入时间
time.sleep(3)

foo.dfd.callback('3秒过去了!')

foo.func()

在标准输出下,一个具有:

  $开始了! 
$结果停止:3秒过去了!
$开始!
$停止结果:无

在我的情况下,我希望 func reactor 线程中一次又一次地被调用。有什么方法可以确保 yield 调用始终返回延迟对象的已解析值而不会引入额外状态,如果这样,最优雅的方法是这样做吗?



更新



根据以下建议,我实现了作为装饰者的解决方案:

 导入functools 


def recycles_deferred(deferred_getter):
给出一个返回延迟对象的可调用deferred_getter,创建
另一个函数,该函数返回该延迟对象的'可重用'版本。
@ functools.wraps(deferred_getter)
def _recycler(* args,** kwargs):
old_dfd = deferred_getter(* args,** kwargs)
new_dfd = defer.Deferred()

def _recycle (结果):
new_dfd.callback(结果)
返回结果

old_dfd.addCallback(_recycle)

返回new_dfd

重新如果__name__ =='__main__',则转_recycler




演示如何使用此@recycles_deferred。
从twisted.internet导入时间

延迟

O类(对象):

def __init __(self):
在实践中,这可以表示一个网络请求。
self.dfd = defer.Deferred()

def do_something_with_result(self,result):
print'得到结果:{0}'。format(结果)
返回结果

@recycles_deferred
def deferred_getter(自己):
返回延期。
return self.dfd

@ defer.inlineCallbacks
def do_something_with_deferred(self):
结果= yield self.deferred_getter()

打印'得到内联结果:{0}'。format(结果)


o = O()

o.dfd.addCallback(o.do_som ething_with_result)#得到结果:foo
o.do_something_with_deferred()#得到内联结果:foo
o.dfd.addCallback(o.do_something_with_result)#得到结果:foo

#睡眠3秒,然后解决延迟的
时间.sleep(3)
o.dfd.callback('foo')

o.do_something_with_deferred()#获得内联结果: foo
o.dfd.addCallback(o.do_something_with_result)#得到结果:foo

#对yield的内联调用永远不会返回None
o.do_something_with_deferred()#得到内联结果:foo
o.do_something_with_deferred()#获得内联结果:foo
o.do_something_with_deferred()#获得内联结果:foo


解决方案

问题不在于 Deferred 本身只能使用一次-它可以无限重复使用,从某种意义上说,您可以一直向其添加回调,并且数据将继续流向下一个callbac。 k,以及下一个(如果有)。您所看到的问题是,当您将回调添加到 Deferred 时,其结果将传播到下一个回调



另一个相交的问题是,收益推迟假定 inlineCallbacks 函数消耗了 Deferred -您正在获得它的价值并为此做点事情,因此,为避免不必要的资源利用( Deferred 携带结果的时间长于所需时间),回调将为您提供 yield的结果表达式本身也会返回 None 。我想,如果它返回某种更明确的由 inlineCallbacks 令牌消耗的东西,可能会更容易理解,但事后看来是20/20 :-)。 / p>

但是从某种意义上说,推迟 只能使用一次,也就是说,如果您有一个返回一个 Deferred 的API,它应该返回一个 new 推迟给每个呼叫者。归还它,实际上是将所有权转移给了呼叫者,因为呼叫者可以修改结果,以将其传递给自己的呼叫者。典型的示例是,如果您有一个返回 Deferred 并以某些字节触发的API,但是您知道这些字节应该是JSON,则可以添加 .addCallback(json.loads)然后返回它,这将允许调用方使用JSON序列化的对象而不是字节。



因此,如果您打算多次调用 async ,则您将采用以下方式:


$ b来自__future__的$ b

 导入print_function,unicode_literals 

从twisted.internet导入推迟

类Foo(object):

def __init __(self):
self.dfd = defer.Deferred()

def async(self):
justForThisCall = defer.Deferred()
def callbackForDFD(结果):
justForThisCall.callback(结果)
返回结果
self.dfd.addCallback(callbackForDFD)
return justForThisCall

@def er.inlineCallbacks
def func(self):
print('Started!')
结果= yield self.async()
print('结果停止:{0} '.format(result))

如果__name__ =='__main__':
foo = Foo()
print( calling func)
foo.func ()
print( firing dfd)
foo.dfd.callback('无需等待!')
print(再次调用func)
foo.func ()
print( done)

应该会产生以下输出:

 调用func 
已开始!
fireing dfd
结果停止:无需等待!
再次调用func
开始了!
结果停止:无需等待!
完成


In Twisted, it seems that a deferred object can only be used once after its callback has fired, as opposed to other "promise"-based libraries I've worked with:

from twisted.internet import defer


class Foo(object):

    def __init__(self):
        self.dfd = defer.Deferred()

    def async(self):
        return self.dfd

    @defer.inlineCallbacks
    def func(self):
        print 'Started!'

        result = yield self.async()

        print 'Stopped with result: {0}'.format(result)

if __name__ == '__main__':
    foo = Foo()
    foo.func()

    import time
    time.sleep(3)

    foo.dfd.callback('3 seconds passed!')

    foo.func()

On standard out, one has:

$ Started!
$ Stopped with result: 3 seconds passed!
$ Started!
$ Stopped with result: None

In my situation, I'm expecting to func to be called again and again within the reactor thread. Is there any way to ensure that the yield call will always return the "resolved" value of the deferred object without introducing extra state, and if so, what's the most elegant way to do so?

UPDATE

Based on the advice below, I implemented a solution as a decorator:

import functools


def recycles_deferred(deferred_getter):
    """Given a callable deferred_getter that returns a deferred object, create
    another function that returns a 'reusable' version of that deferred object."""
    @functools.wraps(deferred_getter)
    def _recycler(*args, **kwargs):
        old_dfd = deferred_getter(*args, **kwargs)
        new_dfd = defer.Deferred()

        def _recycle(result):
            new_dfd.callback(result)
            return result

        old_dfd.addCallback(_recycle)

        return new_dfd

    return _recycler



if __name__ == '__main__':
    """Demonstration of how this @recycles_deferred should be used."""
    import time

    from twisted.internet import defer

    class O(object):

        def __init__(self):
            """In practice this could representation a network request."""
            self.dfd = defer.Deferred()

        def do_something_with_result(self, result):
            print 'Got result: {0}'.format(result)
            return result

        @recycles_deferred
        def deferred_getter(self):
            """Return the deferred."""
            return self.dfd

        @defer.inlineCallbacks
        def do_something_with_deferred(self):
            result = yield self.deferred_getter()

            print 'Got inline result: {0}'.format(result)


    o = O()

    o.dfd.addCallback(o.do_something_with_result) # Got result: foo
    o.do_something_with_deferred()                # Got inline result: foo
    o.dfd.addCallback(o.do_something_with_result) # Got result: foo

    # sleep 3 seconds, then resolve the deferred
    time.sleep(3)
    o.dfd.callback('foo')

    o.do_something_with_deferred()                # Got inline result: foo
    o.dfd.addCallback(o.do_something_with_result) # Got result: foo

    # the inline call to yield never returns None
    o.do_something_with_deferred() # Got inline result: foo
    o.do_something_with_deferred() # Got inline result: foo
    o.do_something_with_deferred() # Got inline result: foo

解决方案

The issue is not that the Deferred itself can only be "used once" - it's infinitely re-usable, in the sense that you can keep adding callbacks to it forever and data will continue flowing to the next callback, and the next, as it's available. The problem you're seeing is that when you add a callback to a Deferred, its result is propagated to the next callback.

The other, intersecting problem here is that yielding a Deferred from an inlineCallbacks function is assumed to "consume" a Deferred - you're getting its value and doing something with it, so to prevent unnecessary resource utilization (that Deferred carrying around a result for longer than it needs to), the callback that gives you the result from the yield expression also itself returns None. It might be a little easier to understand if it returned some kind of more explicit "consumed by inlineCallbacks token", I suppose, but hindsight is 20/20 :-).

But in a sense, a Deferred can only be "used" once, which is to say, if you have an API which returns a Deferred, it should return a new Deferred to each caller. By returning it, you're really transferring ownership to the caller, because callers may modify the result, to pass on to their own callers. The typical example is that if you have an API that returns a Deferred that fires with some bytes, but you know the bytes are supposed to be JSON, you might add .addCallback(json.loads) and then return it, which would allow that caller to consume the JSON-serialized object rather than the bytes.

So if you intend for async to be called multiple times, the way you would do it is something like this:

from __future__ import print_function, unicode_literals

from twisted.internet import defer

class Foo(object):

    def __init__(self):
        self.dfd = defer.Deferred()

    def async(self):
        justForThisCall = defer.Deferred()
        def callbackForDFD(result):
            justForThisCall.callback(result)
            return result
        self.dfd.addCallback(callbackForDFD)
        return justForThisCall

    @defer.inlineCallbacks
    def func(self):
        print('Started!')
        result = yield self.async()
        print('Stopped with result: {0}'.format(result))

if __name__ == '__main__':
    foo = Foo()
    print("calling func")
    foo.func()
    print("firing dfd")
    foo.dfd.callback('no need to wait!')
    print("calling func again")
    foo.func()
    print("done")

which should produce this output:

calling func
Started!
firing dfd
Stopped with result: no need to wait!
calling func again
Started!
Stopped with result: no need to wait!
done

这篇关于重用Twisted中的延迟对象的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆