How to combine callLater and addCallback?
Asked Answered
E

4

11

This is so broken, I hope you are merciful with me:

reactor.callLater(0, myFunction, parameter1).addCallback(reactor.stop)
reactor.run()

myFunction returns a deferred.

I hope it is clear what I want to do:

  • as soon as the reactor is running, I want to call myFunction. That is why I am using 0 as the delay parameter. Is there no other way except callLater? It looks funny to pass it a delay of 0.
  • I want to stop the reactor as soon as myFunction has completed the task.

The problems that I have so far:

  • AttributeError: DelayedCall instance has no attribute 'addCallback'. Fair enough! How do I put a callback in the callback chain started by myFunction then?
  • exceptions.TypeError: stop() takes exactly 1 argument (2 given).

To solve the second problem I had to define a special function:

def stopReactor(result):
    gd.log.info( 'Result: %s' % result)
    gd.log.info( 'Stopping reactor immediatelly' )
    reactor.stop()

And change the code to:

reactor.callLater(0, myFunction, parameter1).addCallback(stopReactor)
reactor.run()

(still not working because of the callLater problem, but stopReactor will work now)

Is there really no other way to call reactor.stop except by defining an extra function?

Egestion answered 17/11, 2011 at 21:17 Comment(0)
R
24

IReactorTime.callLater and Deferred are mixed together by twisted.internet.task.deferLater.

from twisted.internet import reactor, task

d = task.deferLater(reactor, 0, myFunction, parameter1)
d.addCallback(lambda ignored: reactor.stop())
reactor.run()
Rieth answered 17/11, 2011 at 23:22 Comment(6)
I find this option very readable. The lambda ignored bit looks magic to me: could you clarify what it exactly does?Egestion
Callbacks on a Deferred are called with an argument. reactor.stop does not take any arguments. lambda ignored: reactor.stop() accepts and argument, ignores it, and calls reactor.stop with no arguments.Rieth
It would be a bit more conventional to write: 'lambda _: reactor.stop'Circulation
"ignored" has an obvious meaning, and if it isn't obvious to someone, then it is discoverable. "_" is non-obvious and non-discoverable (and hard to type into a stackoverflow comment box!). It is strictly worse for readability. However, I see that other folks have already approved an edit to my answer which replaced "ignored" with "_". And who am I to argue?Rieth
then how about _ignored?Goeger
"_" is a python convention for ignoredQuintain
O
1

I want to stop the reactor as soon as myFunction has completed the task.

So, create a wrapper that does myFunction's work and then stops the reactor?

def wrapper(reactor, *args):
    myFunction(*args)
    reactor.stop()

reactor.callLater(0, wrapper, reactor, ...)
Once answered 17/11, 2011 at 22:36 Comment(0)
S
0

You need to attach the callback to the deferred that myFunction returns, since callLater doesn't return a function. Something like this might work:

reactor.callLater(0, lambda: myFunction(parameter1).addCallback(lambda _: reactor.stop())

But this is not tested.

You need to write a new function (here the lambda _: reactor.stop()) because callbacks to a deferred always take the result up to then. If you find yourself wanting to use callbacks for their side-effects and you don't care about propagating values often, you could define a little helper function:

def ignoringarg(f):
    return lambda _: f()

And then do:

reactor.callLater(0, lambda: myFunction(paramater1).addCallback(ignoringarg(reactor.stop)))

(What would be really neat would be to define an __rshift__ (and in-place analogue) for the Deferred class so you could do: myFunction(parameter1) >> reactor.stop, for when you want to abandon the argument, or myFunction(parameter1) >>= someotherfunc for when you want to propagate the argument. If you think that abusing haskellish syntax is "neat", anyway.)

Shardashare answered 17/11, 2011 at 22:48 Comment(0)
P
0

If You need to trigger callback with some action, just do it (possibly there's no need to return deferred, or smth). Just to clarify things (using purely deferreds):

from twisted.internet import reactor, defer

# That will be our deferred to play with
# it has callback and errback methods
d = defer.Deferred()

def my_function(x):
    print 'function', x
    # need to trigger deferred upon function run?
    # Lets tell it to do so:
    d.callback(x)

# That's our callback to run after triggering `d`    
def d_callback(y):
    print 'callback ', y

# now let's bind that callback to be actually launched by `d`
d.addCallback(d_callback)

# now adding another callback just to stop reactor
# note lambda simply helps to agree number of arguments
d.addCallback(lambda data: reactor.stop())

# so we'll call `my_function` in 2 secs, then it runs
# then it triggers `d` to fire its callbacks
# then `d` actually detonates the whole chain of its added callbacks

reactor.callLater(2, my_function, 'asdf') # 'asdf' is some stupid param

# Here how it works
print 'Lets start!'
reactor.run()
print 'Done!'
Portecochere answered 14/3, 2013 at 20:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.