osdir.com


[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

How to convert a train running program from synchronous to asynchronous?


> On Apr 26, 2019, at 4:18 AM, Arup Rakshit <ar at zeit.io> wrote:
> 
> I have modelled which starts running once drivers and stations are assigned to it. Otherwise, it doesn?t run, really don?t care if passengers are boarded or not at this moment. :) I think this program can help me to introduce to the Python async programming domain.
> 
> Here is my program:
> 
> # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
> # train.py
> # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
> 
> import time
> import random
> 
> from user import User
> 
> class NotReadyToDeparture(Exception):
>    pass
> 
> class Train:
>    def __init__(self, name, category):
>        self.name = name
>        self.category = category
>        self.__drivers = []
>        self.__stations = []
> 
>    @property
>    def drivers(self):
>        return self.__drivers
> 
>    @drivers.setter
>    def drivers(self, coachmen):
>        self.__drivers = coachmen
> 
>    @property
>    def stations(self):
>        return self.__stations
> 
>    @stations.setter
>    def stations(self, places):
>        self.__stations = places
> 
>    def next_stoppage(self):
>        return self.stations[0]
> 
>    def run(self):
>        total_run_time = 0
>        if len(self.drivers) == 0:
>            raise NotReadyToDeparture('Drivers are not yet boarded')
> 
>        if len(self.stations) == 0:
>            raise NotReadyToDeparture('Stoppage stations are not yet scheduled')
> 
>        for station in range(len(self.stations[:])):
>            run_time_to_next_stoppage = random.randint(2, 6)
>            time.sleep(run_time_to_next_stoppage)
>            total_run_time += run_time_to_next_stoppage
>            print("Train Reached at {0} in {1} seconds".format(self.stations.pop(0), run_time_to_next_stoppage))
> 
>        print(f"Train {self.name} is reached the destination in {total_run_time} seconds.")
> 


The underlying problem with your current "run" method is that it only returns when the train has visited all the stations.  This happens because of your for loop and the call to time.sleep inside your loop.   

Instead, I would suggest that you split the functionality of your current run method into two methods. The "run" method would start the train going.  Then you need a separate method, maybe "update" that would be called continuously.  (Code below is completely untested)

The run method would consist of your current checks to ensure that the drivers and stations are set.  Then, it should set:

     self.total_run_time = 0     # making this an instance variable that will be used in the update method

You also need a way of getting the current (real) time so you can know when you have reached the next station (I'll leave the implementation of that up to you):

      self.stations.insert(0, 'start')   # add something at the beginning of your list that will get popped off immediate at the first call to update
      self.time_at_next_station = <get the current time>     


The update method will be called continuously.  It should check the current time and see if it has reached  the time to move on to the next station, something like:

def update(): 
     currentTime = <get the current time>  
      if currentTime < self.time_at_next_station:  # in transit to the next station, nothing to do
            return

       # Train has arrived at a station, set up for next station
       self.stations.pop(0)      # eliminate the station at the front of the list
       if len(self.stations) == 0:
            return  # reached the end of the line

        run_time_to_next_stoppage = random.randint(2, 6) # whatever time this happened plus some random seconds
        self.time_at_nextStation = currentTime + run_time_to_next_stoppage     # time in the future when train gets to next station
        self.total_run_time = self.total_run_time + run_time_to_next_stoppage  # add to total time
        print('Next stoppage is at', stations[0])
      

The first time "update" is called, it will move from the 'start' to the first station, and calculate when the train should reach the first station.  Because update is called continuously, it only needs to check if the time for getting to the next station has been reached.  If so, then set up for the next station.

That way, your main loop becomes:

train.run()
while len(train.stations) is not 0:     
     train.update()

Then you could have another property to get the total time (and print).

Personally, I would prefer that train.update would return True or False to say if it was done with the whole route.  that is, it would normally return False to say it is not done yet.  But when it gets to the end, it would return True.  Then your main code could be written as:

train.run()
while True:
    done = train.update()
    if done:
         break

Like I said, totally untested, but the idea of splitting into two methods, and eliminating the time.sleep is a better approach.

Irv