user2239318
user2239318

Reputation: 2784

Twisted Matrix Callback call again and again

I need that when a user click a button, that button fire the callback and pass her some parameters, each time, parameters will be different.

I've looked at documentation but appear that the part for looping is missing:

Looping

A common form of dependency is needing to perform the asynchronous operation all over again. The canonical example of this an HTTP redirect: when the callback for a deferred from a page request is returned, it could be the result, or it could be an empty body with the Location HTTP header set, in which case you simply perform the operation over again.

[ here is the HTTP redirect example. It also should have pictures. ]

the if is working, the try also, but the callback run just once

 if happen this:
  try:
     print "I'm here!"
     myobjectx.addCallback(test,x,y,z)  
     myobjectx.callback()

  except:
     ...

Just to get the idea of how this work:

1) create the myobject that do  nothing for now 
2) when an event is fired prepare the callback for the myobject e execute it 
3) how can I redo the callback next time the event happen again?

I'm looking at the pymodbus library example of async client:

https://pymodbus.readthedocs.org/en/latest/examples/asynchronous-client.html

I've got 2 files:

MAINPROCESS
MODBUSLIB

from MAINPROCESS I call

myobjectx = MODBUSLIB.protocol.ClientCreator(reactor, ModbusClientProtocol
        ).connectTCP("localhost", Defaults.Port)

then in a function triggered by an if:

if ('Gigiisclicked' in existkeys):

      myobjectx.addCallback(beginAsynchronousTest)
      myobjectx.callback(beginAsynchronousTest)
      print "executed"

and this is, the print is repeated again and again when the event occours but the callback no.

Upvotes: 1

Views: 459

Answers (1)

Jean-Paul Calderone
Jean-Paul Calderone

Reputation: 48325

I think one misunderstanding here is about how instances of Deferred are meant to be used.

You should think of Deferred as having two different kinds of (albeit highly related) uses.

One use is to be able to publish an event from some code that knows how to notice that events have happened to other code that might be interested to know that the event has happened.

One example of this use is ClientCreator.connectTCP: the implementation of this API knows when a TCP connection attempt has succeeded (or failed) and uses a Deferred to publish this information to other code. Code that is using Deferred like this is code that actually instantiates Deferred (eg, d = Deferred()) and that later uses Deferred.callback and Deferred.errback.

The other use of Deferred is to allow code that is interested in events that has happened to learn that those events have happened. For example, this your application that wants a TCP connection in order to exchange data - but needs to wait while one is being set up before it can proceed. Code that is using Deferred like this is code that uses Deferred.addCallback or Deferred.addErrback (also, Deferred.cancel in recent versions of Twisted).

Deferred.addCallback is the API you use to specify what code to run when a Deferred eventually gets a result.

Deferred.callback is the API that you use to supply a result to a Deferred. And, importantly, a Deferred can only ever be given one result. Each Deferred instance represents the completion of a single operation or the occurrence of a single event.

There are certain some exceptions and some further subtleties but a good rule of thumb is that if your code didn't instantiate the Deferred then your code should not use its callback (or errback) methods. Calling one of those is the job for whatever code created the Deferred.

Given that, I hope it's clear that the use of Deferred APIs in this code has some problems that need to be addressed:

myobjectx = MODBUSLIB.protocol.ClientCreator(reactor, ModbusClientProtocol
    ).connectTCP("localhost", Defaults.Port)

...

if ('Gigiisclicked' in existkeys):

    myobjectx.addCallback(beginAsynchronousTest)
    myobjectx.callback(beginAsynchronousTest)
    print "executed"

Most directly, you shouldn't be calling myobjectx.callback here. That's ClientCreator.connectTCP's job (on top of that, beginAsynchronousTest probably doesn't make sense as a result for this Deferred to have).

Instead, I think you want to use methods of the ModbusClientProtocol instance that ClientCreator.connectTCP will eventually create for you. In the example you linked to, notice that beginAsynchronousTest is defined to accept one argument named client.

Since beginAsynchronousTest is passed to the addCallback method of the Deferred returned by ClientCreator.connectTCP this means it will be called with an instance of the protocol the ClientCreator was initialized with (in this case, ModbusClientProtocol). beginAsynchronousTest will be called as soon as the Deferred is given its result by the implementation of ClientCreator - in other words, it will be called as soon as the connection is established. Setting up a TCP connection takes a somewhat arbitrary amount of time since it involves exchanging data with arbitrary other computers over arbitrary network links - there's no telling how long those resources will take to complete their part of the connection setup.

Once beginAsynchronousTest is called you have a connection - represented by the ModbusClientProtocol instance passed in to it. This is the point in your program where you might be able to start doing multiple things (for example, doing something each time a button is clicked).

At this point the Deferred your program started out with (called myobjectx in the snippets of code above) is done and no longer useful or interesting so you won't be using it anymore.

Instead, you'll be calling methods of ModbusClientProtocol (read_coils or write_coil or whatever else you want to do). Each of these methods probably returns a brand new Deferred representing the result of that particular operation. You'll want to use addCallback with these in order to learn about their results.

The other place people often stumble is figuring out how to make these additional method calls. If you're adding code to the body of beginAsynchronousTest then it's fairly straightforward how to do this:

reading = client.read_coils(1, 1)

However, I suspect you won't want to add your button handling code to the body of beginAsynchronousTest. Instead, you probably have an event handler somewhere else in your program that gets called any time a button has been pressed. Fortunately, it's not much more complicated to deal with this situation.

The key is just to remember that any time you have a reference to the connection you'll be able to use it. Inside the body of beginAsynchronousTest you have a reference to it - the client parameter. You can make this reference available to other parts of your program too: setting an attribute on an object that is shared by the necessary parts of your program is one common, fairly good way to do this.

class ButtonModbusSomething(object):
    def __init__(self):
        self.client = None

    def connect(self):
        creator = MODBUSLIB.protocol.ClientCreator(reactor, ModbusClientProtocol)
        connecting = creator.connectTCP("localhost", Defaults.Port)
        connecting.addCallback(self._connected)
        connecting.addErrback(log.err)

    def _connected(self, client):
        self.client = client

    def buttonClicked(self, existkeys):
        if self.client is not None:
            if "Gigiisclicked" in existkeys:
                self.client.read_coil(1, 1)

Notice how the client attribute of ButtonModbusSomething starts off as None and how buttonClicked needs to check for this case. As mentioned above, setting up a connection can take some time and the only way you know how long is to wait for _connected to be called. This check ensures that if a button is clicked before the connection exists that the event is just ignored (you may want to handle this more nicely - for example, by starting with the user interface in a disabled state and then switching it on only when a connection is set up).

Also, I've left out the code that you probably also want to handle your connection being lost. When this happens, the client attribute is no longer useful. It is still a reference to the ModbusClientProtocol which was connected, but since that protocol instance no longer has a connection it's hard to do anything useful with. You will probably want to re-disable the user interface when the connection is lost or at least start ignoring button presses again.

Also, notice that ClientCreator actually comes from twisted.internet.protocol not MODBUSLIB.protocol.

Upvotes: 3

Related Questions