0

I am developing a simulation of railway signalling and have created a graphical visualisation of the output. There are many instances of a "signal" object. Upon certain conditions, a delay timer is started in a new thread inside a signal object instance so that the signal shows a certain colour for a period of time, then reverts to another colour.

It doesn't seem possible to carry out graphics actions in any thread other than the main thread, so I have used an instance-level flag to indicate when the timer has expired, and a monitoring loop in the main thread checks these flags often and carries out the graphical changes when the flag value of a signal object indicates "expired".

I don't like this much and I'd rather execute the graphical changes from the same function as the timer, but of course that's not in the main thread so it doesn't work (e.g. https://stackoverflow.com/questions/14694408/runtimeerror-main-thread-is-not-in-main-loop).

I'm aware of the following: (1) threading.main_thread() delivers the main thread, (2) that it is possible to override the run method of a thread object in a subclass, and (3) is_alive() "returns True just before the run() method starts until just after the run() method terminates". If it is possible to see the main thread from inside another thread, and detect when the main thread is not busy, and to override its run method temporarily with something I want it to execute, then I can get my graphical changes done when the timer expires without needing the flags and the wasteful separate checking loop.

These snippets I hope show my current solution.

Timer initiation inside the signal object (self.control_time delivers the timer value argument):

                    self.aspect_locked = 2 # Aspect locked until timer expires.
                    self.tick = th.Timer(self.control_time, self.aspect_unlock)
                    # When the timer runs down, the aspect lock is released, 
                    # but GUI actions cannot be executed from inside a thread
                    # other than the main thread, so the fact can only be 
                    # flagged by the timer thread and must be responded to
                    # elsewhere.
                    self.tick.start()

What happens inside the signal object when the timer runs out:

    def aspect_unlock(self): 
        self.aspect_locked = 3

What happens in the main thread to monitor for expired timers:

            if self.dw and self.timeout:
                for sg in self.sgs: 
                    if sg.sigtype != 'D': sg.ac_route_timeout()
                # Wait a bit.
                self.timeout = False
                tick = Timer(1, self.release_timer)
                tick.start()
    def release_timer(self):
        self.timeout = True

The function inside the signal object that carries out the graphical action.

    def ac_route_timeout(self):
        if self.aspect_locked == 3: # Aspect has been locked for a timer, but 
        # is now unlocked.
            if self.dw: self.sigdw.routeLine.setFill('green')

What I want to do is carry out what's inside ac_route_timeout() directly from within aspect_unlock(), and dispense with the flags and the monitoring loops.

0 Answers0