def publish(self, package, endpoint=Endpoints.RESULT, command=Commands.THREAD_HANDLE): """ Publishes a package to all the subscribers of an endpoint By default, will publish to the RESULT endpoint with a command of THREAD_HANDLE. You may wish to provide a different endpoint or command depending on how the application is set up. """ try: for queue in self.subscriptions[endpoint]: queue.put(Message(self.name, command, package)) except KeyError: logging.error( f"Thread {self.name} trying to publish to endpoint {endpoint} which was not created" ) raise except AttributeError: logging.error( f"Thread {self.name} trying to publish to endpoint {endpoint} which was not created" ) raise except Exception: logging.error( f"Isse publishing to endpoing {endpoint} on thread {self.name}" ) raise
def process_outgoing_messsages(self, message: Message): """Process only the outgoing messages""" # Check sender is not in list of destroyed threads if len(message.sender) > 1: # Append name to front of sender and pass up message.sender = [self.owner, *message.sender] self.output_queue.put(message) else: self.logger.error( f"Sender {message.sender} does not have enough information")
def test_multi_run_thread_puts_message_in_queue(make_thread): m = make_thread(ExampleMultiRunThread, "M") q = Queue() send_subscription_message(m, q) m.start() m.start() m.start() m.start() m.end() min_sleep() assert threading.active_count() == 2 assert q.qsize() == 4 assert q.get() == Message("M", Commands.THREAD_HANDLE, 1) assert q.get() == Message("M", Commands.THREAD_HANDLE, 2) assert q.get() == Message("M", Commands.THREAD_HANDLE, 3) assert q.get() == Message("M", Commands.THREAD_HANDLE, 4)
def _message_handle(self, message: Message): """Takes a slightly different approach to messages as it needs to handle different situations""" # Run garbage collection self.garbage_collect() self.logger.debug( f"MESSAGE Sender: {message.sender}, Command {message.command}, Package: {message.package}" ) # Addressed to self if message.receiver == [self.name]: # Process as normal super()._message_handle(message) # Addressed to child thread elif self.name in message.receiver: # Strip name off front and pass on message.receiver = message.receiver[1:] # Send to appropriate thread input queue if message.receiver[0] in self.active_threads.keys(): self.active_threads[message.receiver[0]].input_queue.put( message) else: # Check sender is not in list of destroyed threads if len(message.sender) > 1: if message.sender[1] not in self.destroyed_threads: # Append name to front of sender and pass up message.sender = [self.owner, *message.sender] self.output_queue.put(message) else: self.logger.error( f"Sender {message.sender[1]} is a destroyed thread") else: self.logger.error( f"Sender {message.sender} does not have enough information" ) # Run garbage collection self.garbage_collect()
def test_single_run_thread_puts_message_in_queue(make_thread): e = make_thread(ExampleSingleRunThread, "E") q = Queue() send_subscription_message(e, q) e.start() min_sleep() assert threading.active_count() == 2 assert q.qsize() == 1 assert q.get() == Message("E", Commands.THREAD_HANDLE, None)
def _thread_end(self, message: Message): # Mark all children to be destroyed self.destroyed_threads = set(self.active_threads.keys()) # Send them all a thread end message for thread_name in self.active_threads.keys(): self.active_threads[thread_name].input_queue.put( Message(self.name, thread_name, "THREAD_END", None)) # Ensure that all child threads have ended before raising end flag while len(self.active_threads.keys()) > 0: # Wait for them to end and delete them self.garbage_collect() self.end_flag.set()
def destroy_thread(self, message: Message): """Send a THREAD_END message to a thread and add it to the list of deleted threads""" try: thread_name = message.package["thread_name"] except KeyError: self.logger( "Could not find thread name to DELETE in message package") if thread_name not in self.active_threads.keys(): self.logger.error( f"Tried to DESTROY thread named {thread_name} but not found in active threads" ) return elif thread_name in self.destroyed_threads: self.logger.error( f"Thread named {thread_name} already marked to be DESTROYED") return # Send the thread end command self.active_threads[thread_name].input_queue.put( Message(self.name, thread_name, "THREAD_END", None)) # Mark out to be destroyed self.destroyed_threads.add(thread_name)
def _message_handle(self, message: Message): """Based on ThreadManager approach to handling messages from the outside and from children Have to do some things manually as they do not have scope for the new functions when being used from super """ self.logger.debug( f"MESSAGE Sender: {message.sender}, Command {message.command}, Package: {message.package}" ) # Addressed to self if message.receiver == self.name or message.receiver == [self.name]: # Process as normal super()._message_handle(message) # Addressed to screen elif self.name in message.receiver: # Strip name off front and pass on message.receiver = message.receiver[1:] # If the screen is active pass the message # Should inactive screens be able to receive messsages? if message.receiver[0] in self.active_screens: self.screens[message.receiver[0]].update(message) else: self.process_outgoing_messsages(message)
def end(self): """Put THREAD_END message in queue""" self.message_queue.put( Message((self.name, ), Commands.THREAD_END, None))
def stop(self): """Put THREAD_STOP message in queue""" self.message_queue.put( Message((self.name, ), Commands.THREAD_STOP, None))
def start(self): """Put THREAD_START message in queue""" self.message_queue.put( Message((self.name, ), Commands.THREAD_START, None))
if message.command == "UPDATE": self.extra_label.configure(text=message.package) if __name__ == "__main__": inq1 = Queue() outq1 = Queue() Manager = "Screen Manager" home_screen = "Screen Manager Example" print("Running Tk Screen Manager") Tk1 = TkScreenManager(Manager, "owner", inq1, outq1) Tk1.input_queue.put(Message("owner", [Manager], "THREAD_START", None)) Tk1.input_queue.put( Message( "owner", [Manager], "NEW_SCREEN", { "screen_name": home_screen, "screen_type": MyFirstGUI }, )) time.sleep(5) Tk1.input_queue.put(Message("owner", [Manager], "SHOW_SCREEN", home_screen)) time.sleep(5)
out_queue = Queue() manager = "Example Manager" owner = "Owner" print(f"Active Threads: {threading.active_count()}") # Create the ThreadManager print("Creating Example Manager") example_manager = ThreadManager(manager, owner, in_queue, out_queue) print(f"Active Threads: {threading.active_count()}") # Supply a dictionary of thread types print("Providing thread types") in_queue.put( Message(owner, [manager], "SET_THREAD_TYPES", {"Single Run": ExampleSingleRunThread})) # Create a new single run thread print("New single run thread") in_queue.put( Message( owner, [manager], "NEW_THREAD", { "thread_name": "S1", "thread_type": "Single Run" }, )) time.sleep(0.1) print(f"Active Threads: {threading.active_count()}")
def quit_command(self): # Directly call the end thread function self._thread_end(Message(None, None, None, None))
if __name__ == "__main__": in_queue = Queue() out_queue = Queue() # See that all the threads are running print(f"Active threads: {threading.active_count()}") # Create the ThreadManager example_manager = ThreadManager("Example Manager", "Owner", in_queue, out_queue) # Set the thread types with human readable names in_queue.put( Message( "Owner", ["Example Manager"], "SET_THREAD_TYPES", {"Multi Run": ExampleMultiRunThread}, )) # Create multipe Multi Run thread instances in_queue.put( Message( "Owner", ["Example Manager"], "NEW_THREAD", { "thread_name": "Multi 1", "thread_type": "Multi Run" }, )) in_queue.put(
def send_subscription_message(thread: SimpleThread, queue, endpoint=SimpleThread.Endpoints.RESULT): thread.message_queue.put( Message("Test", Commands.THREAD_SUBSCRIBE, SubscriptionPackage(endpoint, queue)))
def send_to(self, receiver: List[str], command: str, package: any): """Helper function for sending information from a thread correctly""" self.output_queue.put( Message([self.owner, self.name], receiver, command, package))
if __name__ == "__main__": inq1 = Queue() outq1 = Queue() Manager = "Screen Manager" choice_screen = "Choice" blue_screen = "Blue" red_screen = "Red" print("Running Tk Screen Manager") Tk1 = TkScreenManager(Manager, "owner", inq1, outq1) Tk1.input_queue.put(Message("owner", [Manager], "THREAD_START", None)) Tk1.input_queue.put( Message( "owner", [Manager], "NEW_SCREEN", {"screen_name": choice_screen, "screen_type": ChoiceScreen}, ) ) Tk1.input_queue.put( Message( "owner", [Manager], "NEW_SCREEN", {"screen_name": blue_screen, "screen_type": BlueScreen}, )
def repeating_start(self): """Command to set the *main* function going again""" self.message_queue.put(Message(self.name, "THREAD_REPEAT", None))
flash_screen = "Flash Screen" flash_thread = "Flash Thread" print(f"Active Threads: {threading.active_count()}") # Create the ThreadManager print("Creating Example Manager") example_manager = ThreadManager(manager, owner, in_queue, out_queue) print(f"Active Threads: {threading.active_count()}") # Supply a dictionary of thread types print("Providing thread types") in_queue.put( Message( owner, [manager], "SET_THREAD_TYPES", {"SCREEN": TkScreenManager, "FLASH_THREAD": FlashThread}, ) ) # Create Threads in_queue.put( Message( owner, [manager], "NEW_THREAD", {"thread_name": screen_manager_1, "thread_type": "SCREEN"}, ) ) in_queue.put( Message(