Categories
Blather Programming Python Software

Our Eye-beams Begin to Twist

Part 3: Our Eye-beams Begin to Twist

This continues the introduction started here. You can find an index to the entire series here.

Doing Nothing, the Twisted Way

Eventually we are going to re-implement our asynchronous poetry client using Twisted. But first let’s write a few really simple Twisted programs just to get the flavor of things. As I mentioned in Part 2, I developed these examples using Twisted 8.2.0. Twisted APIs do change, but the core APIs we are going to use will likely change slowly, if at all, so I expect these examples to work for many future releases. If you don’t have Twisted installed you can obtain it here.

The absolute simplest Twisted program is listed below, and is also available in basic-twisted/simple.py in the base directory of the twisted-intro example code.

from twisted.internet import reactor
reactor.run()

You can run it like this:

python basic-twisted/simple.py

As we saw in Part 2, Twisted is an implementation of the Reactor Pattern and thus contains an object that represents the reactor, or event loop, that is the heart of any Twisted program. The first line of our program imports the reactor object so we can use it, and the second line tells the reactor to start running the loop.

This program just sits there doing nothing. You’ll have to stop it by pressing Control-C, otherwise it will just sit there forever. Normally we would have given the loop one or more file descriptors (connected to, say, a poetry server) that we want to monitor for I/O. We’ll see how to do that later, but for now our reactor loop is stuck. Note that this is not a busy loop which keeps cycling over and over. If you happen to have a CPU meter on your screen, you won’t see any spikes caused by this technically infinite loop. In fact, our program isn’t using any CPU at all. Instead, the reactor is stuck at the top cycle of Figure 5, waiting for an event that will never come (to be specific, waiting on a select call with no file descriptors).

That might make for a compelling metaphor of Hamletian inaction, but it’s still a pretty boring program. We’re about to make it more interesting, but we can already draw a few conclusions:

  1. Twisted’s reactor loop doesn’t start until told to. You start it by calling reactor.run().
  2. The reactor loop runs in the same thread it was started in. In this case, it runs in the main (and only) thread.
  3. Once the loop starts up, it just keeps going. The reactor is now “in control” of the program (or the specific thread it was started in).
  4. If it doesn’t have anything to do, the reactor loop does not consume CPU.
  5. The reactor isn’t created explicitly, just imported.

That last point is worth elaborating on. In Twisted, the reactor is basically a Singleton. There is only one reactor object and it is created implicitly when you import it. If you open the reactor module in the twisted.internet package you will find very little code. The actual implementation resides in other files (see, for example, twisted.internet.selectreactor).

Twisted actually contains multiple reactor implementations. As mentioned in Part 2, the select call is just one method of waiting on file descriptors. Twisted includes several reactor implementations that use a variety of different methods. For example, twisted.internet.pollreactor uses the poll system call instead of select.

To use a specific reactor, you must install it before importing twisted.internet.reactor. Here is how you install the pollreactor:

from twisted.internet import pollreactor
pollreactor.install()

If you import twisted.internet.reactor without first installing a specific reactor implementation, then Twisted will install the default reactor for you. The particular one you get will depend on the operating system and Twisted version you are using. For that reason, it is general practice not to import the reactor at the top level of modules to avoid accidentally installing the default reactor. Instead, import the reactor in the same scope in which you use it.

Note: as of this writing, Twisted has been moving gradually towards an architecture which would allow multiple reactors to co-exist. In this scheme, a reactor object would be passed around as a reference rather than imported from a module.

Note: not all operating systems support the poll call. If that is the case for your system, this example will not work.

Now we can re-implement our first Twisted program using the pollreactor, as found in basic-twisted/simple-poll.py:

from twisted.internet import pollreactor
pollreactor.install()

from twisted.internet import reactor
reactor.run()

And we have a poll loop that does nothing at all instead of a select loop that does nothing at all. Neato.

We’re going to stick with the default reactor for the rest of this introduction. For the purposes of learning Twisted, all the reactors do the same thing.

Hello, Twisted

Let’s make a Twisted program that at least does something. Here’s one that prints a message to the terminal window, after the reactor loop starts up:

def hello():
print 'Hello from the reactor loop!'
print 'Lately I feel like I\'m stuck in a rut.'

from twisted.internet import reactor

reactor.callWhenRunning(hello)

print 'Starting the reactor.'
reactor.run()

This program is in basic-twisted/hello.py. If you run it, you will see this output:

Starting the reactor.
Hello from the reactor loop!
Lately I feel like I'm stuck in a rut.

You’ll still have to kill the program yourself, since it gets stuck again after printing those lines.

Notice the hello function is called after the reactor starts running. That means it is called by the reactor itself, so Twisted code must be calling our function. We arrange for this to happen by invoking the reactor method callWhenRunning with a reference to the function we want Twisted to call. And, of course, we have to do that before we start the reactor.

We use the term callback to describe the reference to the hello function. A callback is a function reference that we give to Twisted (or any other framework) that Twisted will use to “call us back” at the appropriate time, in this case right after the reactor loop starts up. Since Twisted’s loop is separate from our code, most interactions between the reactor core and our business logic will begin with a callback to a function we gave to Twisted using various APIs.

We can see how Twisted is calling our code using this program:

import traceback

def stack():
    print 'The python stack:'
    traceback.print_stack()

from twisted.internet import reactor
reactor.callWhenRunning(stack)
reactor.run()

You can find it in basic-twisted/stack.py and it prints out something like this:

The python stack:
...
  reactor.run() <-- This is where we called the reactor
...
...  <-- A bunch of Twisted function calls
...
  traceback.print_stack() <-- The second line in the stack function

Don’t worry about all the Twisted calls in between. Just notice the relationship between the reactor.run() call and our callback.

What’s the deal with callbacks?

Twisted is not the only reactor framework that uses callbacks. The older asynchronous Python frameworks Medusa and asyncore also use them. As do the GUI toolkits GTK and QT, both based, like many GUI frameworks, on a reactor loop.

The developers of reactive systems sure love callbacks. Maybe they should just marry them. Maybe they already did. But consider this:

  1. The reactor pattern is single-threaded.
  2. A reactive framework like Twisted implements the reactor loop so our code doesn’t have to.
  3. Our code still needs to get called to implement our business logic.
  4. Since it is “in control” of the single thread, the reactor loop will have to call our code.
  5. The reactor can’t know in advance which part of our code needs to be called.

In this situation callbacks are not just one option — they are the only real game in town.

Figure 6 shows what happens during a callback:

Figure 6: the reactor making a callback
Figure 6: the reactor making a callback

Figure 6 illustrates some important properties of callbacks:

  1. Our callback code runs in the same thread as the Twisted loop.
  2. When our callbacks are running, the Twisted loop is not running.
  3. And vice versa.
  4. The reactor loop resumes when our callback returns.

During a callback, the Twisted loop is effectively “blocked” on our code. So we should make sure our callback code doesn’t waste any time. In particular, we should avoid making blocking I/O calls in our callbacks. Otherwise, we would be defeating the whole point of using the reactor pattern in the first place. Twisted will not take any special precautions to prevent our code from blocking, we just have to make sure not to do it. As we will eventually see, for the common case of network I/O we don’t have to worry about it as we let Twisted do the asynchronous communication for us.

Other examples of potentially blocking operations include reading or writing from a non-socket file descriptor (like a pipe) or waiting for a subprocess to finish. Exactly how you switch from blocking to non-blocking operations is specific to what you are doing, but there is often a Twisted API that will help you do it. Note that many standard Python functions have no way to switch to a non-blocking mode. For example, the os.system function will always block until the subprocess is finished. That’s just how it works. So when using Twisted, you will have to eschew os.system in favor of the Twisted API for launching subprocesses.

Goodbye, Twisted

It turns out you can tell the Twisted reactor to stop running by using the reactor’s stop method. But once stopped the reactor cannot be restarted, so it’s generally something you do only when your program needs to exit.

Note: there has been past discussion on the Twisted mailing list about making the reactor “restartable” so it could be started and stopped as you like. But as of version 8.2.0, you can only start (and thus stop) the reactor once.

Here’s a program, listed in basic-twisted/countdown.py, which stops the reactor after a 5 second countdown:

class Countdown(object):

    counter = 5

    def count(self):
        if self.counter == 0:
            reactor.stop()
        else:
            print self.counter, '...'
            self.counter -= 1
            reactor.callLater(1, self.count)

from twisted.internet import reactor

reactor.callWhenRunning(Countdown().count)

print 'Start!'
reactor.run()
print 'Stop!'

This program uses the callLater API to register a callback with Twisted. With callLater the callback is the second argument and the first argument is the number of seconds in the future you would like your callback to run. You can use a floating point number to specify a fractional number of seconds, too.

So how does Twisted arrange to execute the callback at the right time? Since this program doesn’t listen on any file descriptors, why doesn’t it get stuck in the select loop like the others? The select call, and the others like it, also accepts an optional timeout value. If a timeout value is supplied and no file descriptors have become ready for I/O within the specified time then the select call will return anyway. Incidentally, by passing a timeout value of zero you can quickly check (or “poll”) a set of file descriptors without blocking at all.

You can think of a timeout as another kind of event the event loop of Figure 5 is waiting for. And Twisted uses timeouts to make sure any “timed callbacks” registered with callLater get called at the right time. Or rather, at approximately the right time. If another callback takes a really long time to execute, a timed callback may be delayed past its schedule. Twisted’s callLater mechanism cannot provide the sort of guarantees required in a hard real-time system.

Here is the output of our countdown program:

Start!
5 ...
4 ...
3 ...
2 ...
1 ...
Stop!

Note the “Stop!” line at the ends shows us that when the reactor exits, the reactor.run call returns. And we have a program that stops all by itself.

Take That, Twisted

Since Twisted often ends up calling our code in the form of callbacks, you might wonder what happens when a callback raises an exception. Let’s try it out. The program in basic-twisted/exception.py raises an exception in one callback, but behaves normally in another:

def falldown():
    raise Exception('I fall down.')

def upagain():
    print 'But I get up again.'
    reactor.stop()

from twisted.internet import reactor

reactor.callWhenRunning(falldown)
reactor.callWhenRunning(upagain)

print 'Starting the reactor.'
reactor.run()

When you run it at the command line, you will see this output:

Starting the reactor.
Traceback (most recent call last):
  ... # I removed most of the traceback
exceptions.Exception: I fall down.
But I get up again.

Notice the second callback runs after the first, even though we see the traceback from the exception the first raised. And if you comment out the reactor.stop() call, the program will just keep running forever. So the reactor will keep going even when our callbacks fail (though it will report the exception).

Network servers generally need to be pretty robust pieces of software. They’re not supposed to crash whenever any random bug shows its head. That’s not to say we should be lackadaisical when it comes to handling our own errors, but it’s nice to know Twisted has our back.

Poetry, Please

Now we’re ready to grab some poetry with Twisted. In Part 4, we will implement a Twisted version of our asynchronous poetry client.

Suggested Exercises

  1. Update the countdown.py program to have three independently running counters going at different rates. Stop the reactor when all counters have finished.
  2. Consider the LoopingCall class in twisted.internet.task. Rewrite the countdown program above to use LoopingCall. You only need the start and stop methods and you don’t need to use the “deferred” return value in any way. We’ll learn what a “deferred” value is in a later Part.

149 replies on “Our Eye-beams Begin to Twist”

”’
Exercise 1
”’

from twisted.internet import reactor

class Countdown(object):
counter = counter2 = counter3 = 5

def count(self):
if self.counter == 0:
reactor.callLater(.5,self.stopSignal)
reactor.stop()
else:
print self.counter, '...', 'counter'
self.counter -= 1
reactor.callLater(2,self.count)

def count2(self):
if self.counter2 != 0:
print self.counter2, '...', 'counter2'
self.counter2 -= 1
reactor.callLater(1,self.count2)

def count3(self):
if self.counter3 != 0:
print self.counter3, '...', 'counter3'
self.counter3 -= 1
reactor.callLater(0.5,self.count3)

def stopSignal(self):
if (self.counter+self.counter2+self.counter3)==0:
reactor.stop()

reactor.callWhenRunning(Countdown().count) # runs immediately when reactor starts
reactor.callWhenRunning(Countdown().count2) # runs 2nd when reactor starts
reactor.callWhenRunning(Countdown().count3) # runs 3rd when reactor starts

print ‘start!’
reactor.run()
print ‘stop!’

I thought this would be easy but turned out much harder than I thought. Hope this is right.

Looks like you have to kick off other counters from same function to get it to “see” the progress…

— code —

from twisted.internet import reactor

class Countdown(object):
counter1 = counter2 = counter3 = 5 # loops
rate1=.5 # sec
rate2=.1 # sec
rate3=.7 # sec
startcount2 = startcount3 = 0 # only start count2 and count3 once
mc1=mc2=mc3=0 # counter message initialization

def count(self):
if self.startcount2 == 0:
reactor.callLater(.01,self.count2) # start count2 immediately after count
self.startcount2 = 1
if self.startcount3 == 0:
reactor.callLater(.01,self.count3) # start count3 immediately after count2
self.startcount3 = 1
if self.counter1 == 0:
a=1
reactor.callLater(.1,self.stopSignal)
else:
print self.counter1, '...', 'counter 1'
self.counter1 -= 1
reactor.callLater(self.rate1,self.count) # 5..4..3..2..1

def count2(self):
if self.counter2 == 0:
a=1
reactor.callLater(.1,self.stopSignal)
elif self.counter2 != 0:
print self.counter2, '...', 'counter 2'
self.counter2 -= 1
reactor.callLater(self.rate2,self.count2)

def count3(self):
if self.counter3 == 0:
a=1
reactor.callLater(.1,self.stopSignal)
elif self.counter3 != 0:
print self.counter3, '...', 'counter 3'
self.counter3 -= 1
reactor.callLater(self.rate3,self.count3)

def stopSignal(self):

if (self.counter1 == 0) & (self.mc1==0):
print 'counter 1 finished...'
self.mc1 = 1
if (self.counter2 == 0) & (self.mc2==0):
print 'counter 2 finished...'
self.mc2 = 1
if (self.counter3 == 0) & (self.mc3==0):
print 'counter 3 finished...'
self.mc3 = 1
if (self.counter1+self.counter2+self.counter3)==0:
reactor.stop()

reactor.callWhenRunning(Countdown().count) # runs immediately when reactor starts

print ‘start!’
reactor.run()
print ‘stop!’

I think you are on the right track here. But let me suggest making the countdown callback a method of Counter, and only having one version of it instead of three. You can use a class variable to keep track of all the Counters that have been created so far, so you know when to stop. Do you see what I mean?

How about this:

— code —

from twisted.internet import reactor

class Countdown(object):

def __init__(self):
rates = [.1,.2,.5,.7,1] # countdown timer rates
Counter = 5 # can be array for different countdown timers
timeStop = max(rates)*Counter
for i in range(len(rates)):
self.count(rates[i],Counter)
reactor.callLater(timeStop,reactor.stop)

def count(self,Rate,Counter):
if Counter == 0:
return Counter
else:
print Counter, '...'
Counter -= 1
reactor.callLater(Rate,self.count,Rate,Counter) # 5..4..3..2..1

reactor.callWhenRunning(Countdown) # runs immediately when reactor starts

print ‘start!’
reactor.run()
print ‘stop!’

Cool, I like that there’s just one count method now, that’s much better. I have some more suggestions. Feel free to ignore them if you feel like moving on to other exercises.

  • Have a single Countdown instance manage one countdown series and instantiate a new one for each different series.
  • Make the reactor shutdown triggered by the last countdown coming to a stop, instead of arranging for the timing to work out. Timing isn’t going to be a robust way of ensuring that the reactor stops only after all countdowns have stopped.
  • Separate the countdown code from the code that decides it’s time to stop the reactor. A countdown is a local object that knows how to count down at a certain rate, but doesn’t need to know the global state of the whole program. The code that starts the countdowns is the logical place for the code to stop the reactor, because that’s the code that “knows” how many countdowns were started in the first place (since it started them). So how does that code know when a single countdown stops? What about passing a callback to the Countdown that it will call when it is done?

class Countdown(object):

counter1 = 5
counter2 = 20
counter3 = 50

def count1(self):
if self.counter1 == 0 and self.counter2 == 0 and self.counter3 == 0:
reactor.stop()
elif self.counter1 > 0:
print self.counter1, '... process',1
self.counter1 -= 1
reactor.callLater(0.1, self.count1)

def count2(self):
if self.counter1 == 0 and self.counter2 == 0 and self.counter3 == 0:
reactor.stop()
elif self.counter2 > 0:
print self.counter2, '... process',2
self.counter2 -= 1.25
reactor.callLater(0.12, self.count2)

def count3(self):
if self.counter1 == 0 and self.counter2 == 0 and self.counter3 == 0:
reactor.stop()
elif self.counter3 > 0:
print self.counter3, '... process',3
self.counter3 -= 12.5
reactor.callLater(0.2345, self.count3)

from twisted.internet import reactor

obj = Countdown()
reactor.callWhenRunning(obj.count1)
reactor.callWhenRunning(obj.count2)
reactor.callWhenRunning(obj.count3)

print ‘Start!’
reactor.run()
print ‘Stop!’

Hi Dave,

Sorry the website didn’t allow me to reply to your last reply so I am posting here. I incorporated many of your suggestions. Is this closer to what you had in mind?

Thanks for being patient!

— code —

from twisted.internet import reactor

class Countdown(object):

def __init__(self,Counter,Speed):
self.__Runs=0
self.__Counter=Counter
self.__Speed=Speed
for i in range(len(self.__Speed)):
self.count(self.__Speed[i],self.__Counter)

def count(self,Rate,Counter):
if Counter == 0:
return Countdown.finish(self)
else:
print Counter, '...'
Counter -= 1
reactor.callLater(Rate,self.count,Rate,Counter)

def finish(self):
self.__Runs +=1
if self.__Runs == len(self.__Speed):
reactor.stop()

reactor.callWhenRunning(Countdown,5,[.1,.2,.5])

print ‘start!’
reactor.run()
print ‘stop!’

That’s definitely more like it. Let me know what you think of this version, where the code to stop the reactor isn’t part of the Countdown object:

from twisted.internet import reactor

class Countdown(object):

def __init__(self, count, speed, on_finished):
    self.count = count
    self.speed = speed
    self.on_finished = on_finished
    self.countdown()

def countdown(self):
    if self.count <= 0:
        self.on_finished(self)
    else:
        print self.count, '...'
        self.count -= 1
        reactor.callLater(self.speed, self.countdown)

def start_counters(count, speeds):
    countdowns = []

    def countdown_finished(countdown):
        countdowns.remove(countdown)
        if not countdowns:
            reactor.stop()

    for speed in speeds:
        countdowns.append(Countdown(count, speed, countdown_finished))

reactor.callWhenRunning(start_counters, 5, [0.1, 0.2, 0.5])

print 'start!'
reactor.run()
print 'stop!'

Maybe its due to the indentation but I haven’t been able to make the above code work. Where is the ‘for loop’ actually placed in the code. Whether it is inside or outside the start_counter() function, it has not been able to execute and the reactor does not stop. The self.count never gets to be printed either.

Works perfectly now. Thanks!
I had actually missed the self.countdown() statement in the init method during typing the code, which meant that the countdown never started.

For exercise-1 I used your suggestion. Is it okay to use global variables like that ?

class Countdown(object):
def init(self,title,counter,interval):
self.counter = counter
self.title = title
self.interval = interval
def count(self):
if self.counter == 0:
reactor.callLater(1,globCounter)
else:
print self.title, self.counter, ‘…’
self.counter -= 1
reactor.callLater(self.interval, self.count)

from twisted.internet import reactor

mcounter = 3

def globCounter():
global mcounter
mcounter = mcounter -1
if mcounter == 0:
reactor.stop()

reactor.callWhenRunning(Countdown(“First Counter: “,5,0.1).count)
reactor.callWhenRunning(Countdown(“Second Counter: “,7,2).count)
reactor.callWhenRunning(Countdown(“Third Counter: “,3,1.2).count)
print ‘Start!’
reactor.run()
print ‘Stop!’

Nice! I think it’s fine for a simple exercise, but if you want to keep working on it you might consider how to get rid of the global (and also how you could avoid having Countdown objects “know” about the globCounter function).

class Countdown(object):

instances = []

counter = 5

def __init__(self, tick):
self.tick = tick
self.instances.append(self)

def count(self):
if self.counter > 0:
print self.counter, '...'
self.counter -= 1
reactor.callLater(self.tick, self.count)
else:
self.instances.remove(self)

if not self.instances:
reactor.stop()

from twisted.internet import reactor

reactor.callWhenRunning(Countdown(1).count)
reactor.callWhenRunning(Countdown(.5).count)
reactor.callWhenRunning(Countdown(.3).count)

print ‘Start!’
reactor.run()
print ‘Stop!’

class Counter(object):

def __init__(self, counter, rate, name):
self.name = name
self.counter = counter
self.rate = rate
self.counting = 1

def count(self):
if self.counter == 0:
self.counting = 0
print self.name, ' : ', 'Done!'
else:
print self.name, ' : ', self.counter, '...'
self.counter -= self.rate
reactor.callLater(1, self.count)

class Countdown2(object):

counts = [20, 20, 20]
rates = [1, 2, 5]
names = ['1st', '2nd', '3rd']

counters = [Counter(c, r, n) for c, r, n
in zip(counts, rates, names)]

def is_counting(self):
return sum([counter.counting for counter
in self.counters])

def shutdown(self):

if self.is_counting():
reactor.callLater(1, self.shutdown)
else:
print 'All done!'
reactor.stop()

def update(self):
for counter in self.counters:
counter.count()

from twisted.internet import reactor

ctd = Countdown2()

reactor.callWhenRunning(ctd.update)
reactor.callWhenRunning(ctd.shutdown)

print ‘Start!’
reactor.run()
print ‘Stop!’

Hi Dave,
I have never come across such a nicely written article. It is so well written, that even if I dont intend to use Twisted, still I would read the article for the joy of reading it.

Thanks a lot.

Hi Dave,

I also had a go on exercise one, it closely came to your reply here link. While I thought the exercise asks for separate countdowns… which led me to understanding that inside the method calls data is not kept, hence the start-1 bit.

class Countdown(object):
    rgd_counters = 0

    def register_counter(self):
        self.rgd_counters += 1

    def rm_counter(self):
        self.rgd_counters -= 1
        if self.rgd_counters == 0:
            reactor.stop()

    def init_counter(self, start, my_wait):
        self.register_counter()
        reactor.callLater(my_wait, self.count, start, my_wait)

    def count(self, start, my_wait):
        if start == 0:
            self.rm_counter()
        else:
            print start, '...', my_wait
            reactor.callLater(my_wait, self.count, start - 1, my_wait)

from twisted.internet import reactor

my_counter = Countdown()
reactor.callWhenRunning(my_counter.init_counter, 5, 1)
reactor.callWhenRunning(my_counter.init_counter, 5, 2)
reactor.callWhenRunning(my_counter.init_counter, 5, 5)

print 'Start!'
reactor.run()
print 'Stop!'

Hi Dave,

first i want to thank you for this great tutorial, this is the first time i understands the difference between synchronous and asynchronous is an easy way, and i used it before in the Qt library, it is asynchronous by default

here is my try on Exc 1, added time to see the point you mentioned before

from twisted.internet import reactor
from datetime import datetime


class Countdown(object):

    counter = [
        {'name': 'bot001', 'time': 1, 'count': 20},
        {'name': 'bot002', 'time': 2, 'count': 5},
        {'name': 'bot003', 'time': .5, 'count': 10},
        {'name': 'bot004', 'time': .1, 'count': 20},
    ]

    started = []

    finish = len(counter)

    def count(self, cnt):
        if self.finish == 0:
            reactor.stop()
        else:
            print('count for {', self.counter[cnt]['time'], '} is : ...', self.counter[cnt]['count'])
            self.counter[cnt]['count'] -= 1
            if self.counter[cnt]['count'] >= 0:
                reactor.callLater(self.counter[cnt]['time'], self.count, cnt)
                if self.counter[cnt]['count'] == 0:
                    self.finish -= 1
                    print(self.counter[cnt]['name'], 'Task DONE'
                          , (datetime.now() - self.started[cnt]).total_seconds())

    def count_run(self):
        for i in list(range(len(self.counter))):
            self.started.append(datetime.now())
            self.count(i)
            if self.finish == 0:
                reactor.stop()


if __name__ == '__main__':
    reactor.callWhenRunning(Countdown().count_run)

    print('Start!')
    reactor.run()
    print('Stop!')

Hi Dave,

Here is my try on exercise 1. It is far from ideal, but at least if uses a callback to check if all counters did finish as you suggested above.

from twisted.internet import reactor

class Countdown(object):

def __init__(self, name, counter, rate):
self.name = name
self.counter = counter
self.rate = rate

def count(self):
if self.counter == 0:
reactor.callWhenRunning(finished)
if self.counter > 0:
print self.name, '...', self.counter, '... process'
self.counter -= 1
reactor.callLater(self.rate, self.count)

def check_to_stop(counters):
for c in counters:
c.count()

def finished():
total = sum([c.counter for c in counters])
if total == 0:
reactor.stop()

counters = [
Countdown('Counter 1', 20, 0.3),
Countdown('Counter 2', 5, 1),
Countdown('Counter 3', 4, 0.5),
Countdown('Counter 4', 10, 0.375),
Countdown('Counter 5', 50, 0.1),
]
reactor.callWhenRunning(check_to_stop, counters)

print 'Start!'
reactor.run()
print 'Stop!'

class Countdown:

CountdownNum = 1

def __init__(self, counter, when):
self.counter = counter
self.when = when
self.activeCounts = Countdown.CountdownNum
Countdown.CountdownNum += 1

def count(self):
if self.counter == 0:
Countdown.CountdownNum -= 1
if Countdown.CountdownNum == 1:
reactor.stop()
else:
print 'Counter number', self.activeCounts, ':', self.counter, '...'
self.counter -= 1
reactor.callLater(self.when, self.count)

from twisted.internet import reactor

reactor.callWhenRunning(Countdown(50, 0.01).count)
reactor.callWhenRunning(Countdown(25, 0.02).count)
reactor.callWhenRunning(Countdown(16, 0.03).count)
reactor.callWhenRunning(Countdown(12, 0.04).count)
reactor.callWhenRunning(Countdown(10, 0.05).count)

print ‘Start!’
reactor.run()
print ‘Stop!’

Maybe a little late to reply.

class Countdown(object):

def __init__(self, rates, **kws):
self.rates = rates
self.counters = kws.get('counters', [5] * len(rates))

def start_count(self):
def wrapper_count(id):
if self.counters[id] != 0:
print(f'Counter {id}: {self.counters[id]}')
self.counters[id] -= 1
reactor.callLater(self.rates[id], wrapper_count, id)
elif sum(self.counters) == 0:
reactor.stop()
for id in range(len(self.counters)):
reactor.callWhenRunning(wrapper_count, id)

from twisted.internet import reactor

reactor.callWhenRunning(Countdown([1,1.2,1.5]).start_count)

print(‘Start!’)
reactor.run()
print(‘Stop!’)

Leave a Reply to yangweinjcnCancel reply

Discover more from krondo

Subscribe now to keep reading and get access to the full archive.

Continue reading