It became necessary to deal with multithreading in the GUI. Please look at my version and tell how far it is from the correct implementation, suggest about the general principles of managing the flow of GUI elements. The program is used to work with the terminal.

ser = serial.Serial('COM4') bills = [0, 0, 10, 50, 100, 500, 1000, 5000, 1, 2, 5, 10] ev = threading.Event() q = multiprocessing.Queue() def polling_loop(): while True: # Ρƒ мСня Π΅ΡΡ‚ΡŒ Ρ‡Π°ΡΡ‚ΡŒ ΠΊΠΎΠ΄Π°, которая Π΄ΠΎΠ»ΠΆΠ½Π° выполнятся ΠΎΠ΄ΠΈΠ½ Ρ€Π°Π· ΠΏΡ€ΠΈ ΠΊΠ°ΠΆΠ΄ΠΎΠΌ 'Π²ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠΈ' ΠΏΠΎΡ‚ΠΎΠΊΠ° ev.wait()# ΠΏΡ€ΠΈΠΎΡΡ‚Π°Π½Π°Π²Π»ΠΈΠ²Π°ΡŽ ΠΏΠΎΡ‚ΠΎΠΊ # ΠΈΠ½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·ΠΈΡ€ΡƒΡŽ ΠΊΡƒΠΏΡŽΡ€ΠΎΠΏΡ€ΠΈΠ΅ΠΌΠ½ΠΈΠΊ ser.write(b'\x02\x03\x06\x30\x41\xb3') # здСсь ΠΊΠΎΠ΄, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ выполняСтся ΠΎΠ΄ΠΈΠ½ Ρ€Π°Π· time.sleep(0.2) ser.write(b'\x02\x03\x0c\x34\xff\xff\xff\x00\x00\x00\xb5\xc1') while ev.is_set(): # Π° здСсь ΠΊΠΎΠ΄, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ выполняСтся, # ΠΏΠΎΠΊΠ° я Π½Π΅ ΡΠ±Ρ€ΠΎΡˆΡƒ Ρ„Π»Π°Π³ с event`Π° ΠΊΠ½ΠΎΠΏΠΊΠΎΠΉ # здСсь Π² Ρ†ΠΈΠΊΠ»Π΅ ΠΈΠ΄Π΅Ρ‚ опрос Π½Π° ΠΏΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠ΅ Π±Π°Π½ΠΊΠ½ΠΎΡ‚ ser.write(b'\x02\x03\x06\x00\xc2\x82') ser.write(b'\x02\x03\x06\x33\xda\x81') time.sleep(0.2) if ser.inWaiting() > 0: resp = ser.read(ser.inWaiting()) if resp[3] == 129: q.put(bills[resp[4]]) def stop(): ev.clear() def start(): ev.set() class Window(Tk): def __init__(self, *args, **keywords): super().__init__(self, *args, **keywords) self.geometry('20x200') self.start = Button(text="start", command=lambda: start()) self.stop = Button(text="stop", command=lambda: stop()) self.stop.pack() self.start.pack() th2.start() # ΠΏΠΎΡ‚ΠΎΠΊ этот запускаСтся сразу ΠΏΡ€ΠΈ ΠΈΠ½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·Π°Ρ†ΠΈΠΈ th2 = threading.Thread(target=polling_loop) tk = Window() tk.mainloop() th2.join() 
  • 2
    describe in words what you expect the code to do and what happens instead. If your code works correctly, and you just want a response on a specific implementation of a code for a specific task, still clearly describe what you think it does and add a code inspection code (be sure that the external (observed) code behavior is correct (with your point of view), in case you use this tag) - jfs
  • one
    After closing the window, the thread will continue to work. (I also recommend sticking a fully working code into the question so that I don’t have to think it out myself) andreymal
  • The code works as I need, but I’m not sure that my version is correct, as I didn’t have anything to do with the threads before, but I don’t really like writing according to the principle β€œit works and it’s okay” - Putch

2 answers 2

 import threading, tkinter, time class Window(tkinter.Tk): def __init__(self): tkinter.Tk.__init__(self) self.geometry('20x200') self.is_run = False tkinter.Button(text="start", command=lambda: self.is_run or threading.Thread(target=self.run).start()).pack() tkinter.Button(text="stop", command=self.stop).pack() def stop(self): self.is_run = False def run(self): self.is_run = True print('Ρƒ мСня Π΅ΡΡ‚ΡŒ Ρ‡Π°ΡΡ‚ΡŒ ΠΊΠΎΠ΄Π°, которая Π΄ΠΎΠ»ΠΆΠ½Π° выполнятся ΠΎΠ΄ΠΈΠ½ Ρ€Π°Π· ΠΏΡ€ΠΈ ΠΊΠ°ΠΆΠ΄ΠΎΠΌ Π²ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠΈ ΠΏΠΎΡ‚ΠΎΠΊΠ°') while self.is_run: print('Π° здСсь ΠΊΠΎΠ΄, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ выполняСтся') time.sleep(1) print('Π²Ρ‹Ρ…ΠΎΠ΄') if __name__ == '__main__': Window().mainloop() 

out:

 Ρƒ мСня Π΅ΡΡ‚ΡŒ Ρ‡Π°ΡΡ‚ΡŒ ΠΊΠΎΠ΄Π°, которая Π΄ΠΎΠ»ΠΆΠ½Π° выполнятся ΠΎΠ΄ΠΈΠ½ Ρ€Π°Π· ΠΏΡ€ΠΈ ΠΊΠ°ΠΆΠ΄ΠΎΠΌ Π²ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠΈ ΠΏΠΎΡ‚ΠΎΠΊΠ° Π° здСсь ΠΊΠΎΠ΄, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ выполняСтся Π° здСсь ΠΊΠΎΠ΄, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ выполняСтся Π° здСсь ΠΊΠΎΠ΄, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ выполняСтся Π²Ρ‹Ρ…ΠΎΠ΄ 

or

 import threading, tkinter, time, queue class SThread(threading.Thread): '''worker ΠΏΠΎΡ‚ΠΎΠΊ''' def __init__(self): threading.Thread.__init__(self) self.queue = queue.Queue() self.setDaemon(True) self.start() def run(self): print('thread.run') while True: cmd = self.queue.get(timeout=10) print(cmd) if cmd is None: break cmd['out'] = cmd['target'](*cmd.get('args', ()), **cmd.get('kwargs', {})) self.queue.task_done() class Window(tkinter.Tk): def __init__(self): tkinter.Tk.__init__(self) self.geometry('20x200') self.is_run = False self.thread = SThread() tkinter.Button(text="start", command=lambda: self.is_run or self.thread.queue.put( dict(target=self.run))).pack() tkinter.Button(text="stop", command=self.stop).pack() def stop(self): self.is_run = False def run(self): print('Ρƒ мСня Π΅ΡΡ‚ΡŒ Ρ‡Π°ΡΡ‚ΡŒ ΠΊΠΎΠ΄Π°, которая Π΄ΠΎΠ»ΠΆΠ½Π° выполнятся ΠΎΠ΄ΠΈΠ½ Ρ€Π°Π· ΠΏΡ€ΠΈ ΠΊΠ°ΠΆΠ΄ΠΎΠΌ Π²ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠΈ ΠΏΠΎΡ‚ΠΎΠΊΠ°') self.is_run = True while self.is_run: print('Π° здСсь ΠΊΠΎΠ΄, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ выполняСтся') time.sleep(1) print('Π²Ρ‹Ρ…ΠΎΠ΄') if __name__ == '__main__': Window().mainloop() 

out:

 thread.run {'target': <bound method Window.run of <__main__.Window object at 0x01F19710>>} Ρƒ мСня Π΅ΡΡ‚ΡŒ Ρ‡Π°ΡΡ‚ΡŒ ΠΊΠΎΠ΄Π°, которая Π΄ΠΎΠ»ΠΆΠ½Π° выполнятся ΠΎΠ΄ΠΈΠ½ Ρ€Π°Π· ΠΏΡ€ΠΈ ΠΊΠ°ΠΆΠ΄ΠΎΠΌ Π²ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠΈ ΠΏΠΎΡ‚ΠΎΠΊΠ° Π° здСсь ΠΊΠΎΠ΄, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ выполняСтся Π° здСсь ΠΊΠΎΠ΄, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ выполняСтся Π²Ρ‹Ρ…ΠΎΠ΄ 

or

 import threading, tkinter, time class Window(tkinter.Tk, threading.Thread): def __init__(self): tkinter.Tk.__init__(self) threading.Thread.__init__(self) self.wait = 1 self.setDaemon(True) self.start() self.geometry('20x200') tkinter.Button(text="start", command=self.Start).pack() tkinter.Button(text="stop", command=self.Stop).pack() def destroy(): self.wait = -1 time.sleep(1) self.destroy() self.protocol("WM_DELETE_WINDOW", destroy) def Stop(self): self.wait = 1 def Start(self): self.wait = 0 def run(self): print('thread.start') while self.wait >= 0: if self.wait: time.sleep(self.wait) else: self._target = self.work self._args = () self._kwargs = {} super().run() print('thread.stop') def work(self): print('Ρƒ мСня Π΅ΡΡ‚ΡŒ Ρ‡Π°ΡΡ‚ΡŒ ΠΊΠΎΠ΄Π°, которая Π΄ΠΎΠ»ΠΆΠ½Π° выполнятся ΠΎΠ΄ΠΈΠ½ Ρ€Π°Π· ΠΏΡ€ΠΈ ΠΊΠ°ΠΆΠ΄ΠΎΠΌ Π²ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠΈ ΠΏΠΎΡ‚ΠΎΠΊΠ°') while not self.wait: print('Π° здСсь ΠΊΠΎΠ΄, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ выполняСтся') time.sleep(1) print('Π²Ρ‹Ρ…ΠΎΠ΄') if __name__ == '__main__': Window().mainloop() 

out:

 thread.start Ρƒ мСня Π΅ΡΡ‚ΡŒ Ρ‡Π°ΡΡ‚ΡŒ ΠΊΠΎΠ΄Π°, которая Π΄ΠΎΠ»ΠΆΠ½Π° выполнятся ΠΎΠ΄ΠΈΠ½ Ρ€Π°Π· ΠΏΡ€ΠΈ ΠΊΠ°ΠΆΠ΄ΠΎΠΌ Π²ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠΈ ΠΏΠΎΡ‚ΠΎΠΊΠ° Π° здСсь ΠΊΠΎΠ΄, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ выполняСтся Π° здСсь ΠΊΠΎΠ΄, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ выполняСтся Π²Ρ‹Ρ…ΠΎΠ΄ Ρƒ мСня Π΅ΡΡ‚ΡŒ Ρ‡Π°ΡΡ‚ΡŒ ΠΊΠΎΠ΄Π°, которая Π΄ΠΎΠ»ΠΆΠ½Π° выполнятся ΠΎΠ΄ΠΈΠ½ Ρ€Π°Π· ΠΏΡ€ΠΈ ΠΊΠ°ΠΆΠ΄ΠΎΠΌ Π²ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠΈ ΠΏΠΎΡ‚ΠΎΠΊΠ° Π° здСсь ΠΊΠΎΠ΄, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ выполняСтся Π²Ρ‹Ρ…ΠΎΠ΄ thread.stop 

Super (). Run () method

 def run(self): """Method representing the thread's activity. You may override this method in a subclass. The standard run() method invokes the callable object passed to the object's constructor as the target argument, if any, with sequential and keyword arguments taken from the args and kwargs arguments, respectively. """ try: if self._target: self._target(*self._args, **self._kwargs) finally: # Avoid a refcycle if the thread is running a function with # an argument that has a member that points to the thread. del self._target, self._args, self._kwargs 
  • This option was considered by me, but it does not fit, because the thread can not be started a second time. - Putch
  • why not, run it like this: threading.Thread (target = ThrObj.run) .start (). It is enough to run the object's run method again. - vadim vaduxa
  • threads can only be started once - Putch
  • you did not understand. The .start () method runs once. All the "work" in the .run () method. Stopped run, work stopped, started - continued - vadim vaduxa
  • Thanks a lot! You saved me a lot of time - Putch

Using a background thread to execute a blocking code in a GUI program and threading.Event to pause / launch code from another thread is fully justified. Although, if the Event.wait(timeout) method is not needed in your case, then even a simple True / False flag could be used instead of threading.Event() .

Sometimes a separate thread is not needed at all: work with I / O can be built into the cycle of GUI events. Tkinter provides the Tk.createfilehandler() method on some systems. Sample code that createfilehandler() uses to read output from a subroutine . For comparison, sample code that uses the background thread for the same task β€” pay attention as the Tk.after() method is called to create a loop in the GUI thread (without blocking the thread itself).

A full example that pauses and restarts the loop in a separate thread:

 #!/usr/bin/env python3 import queue import threading def restartable_loop(restarted, results): while restarted.wait(): # wait until the loop is restarted results.put("Ρ‡Π°ΡΡ‚ΡŒ ΠΊΠΎΠ΄Π°, которая Π΄ΠΎΠ»ΠΆΠ½Π° выполнятся ΠΎΠ΄ΠΈΠ½ Ρ€Π°Π· " "ΠΏΡ€ΠΈ ΠΊΠ°ΠΆΠ΄ΠΎΠΌ 'Π²ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠΈ' ΠΏΠΎΡ‚ΠΎΠΊΠ°") while restarted.is_set(): # while it is running results.put('ΠΊΠΎΠ΄, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ выполняСтся, ' 'ΠΏΠΎΠΊΠ° Ρ„Π»Π°Π³ restarted Π½Π΅ пСрСустановлСн') results.put(None) # mark the end of the current batch q = queue.Queue(maxsize=10) restarted = threading.Event() run_loop = restarted.set pause_loop = restarted.clear print('запускаСм ΠΏΠΎΡ‚ΠΎΠΊ') threading.Thread(target=restartable_loop, args=[restarted, q], daemon=True).start() try: q.get(timeout=.5) except queue.Empty: # Π½ΠΈΡ‡Π΅Π³ΠΎ Π½Π΅ Π΄ΠΎΠ»ΠΆΠ½Ρ‹ ΠΏΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ ΠΏΠΎΠΊΠ° Ρ†ΠΈΠΊΠ» Π½Π΅ Π·Π°ΠΏΡƒΡ‰Π΅Π½ pass else: assert 0, "never happens" for _ in range(2): # ΠΏΠΎΠ²Ρ‚ΠΎΡ€ΠΈΠΌ ΠΏΠ°Ρ€Ρƒ Ρ€Π°Π· для дСмонстрации print('запускаСм Ρ†ΠΈΠΊΠ»') run_loop() print(q.get()) # ΠΏΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚ print(q.get()) # ΠΏΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚ print('приостанавливаСм Ρ†ΠΈΠΊΠ»') pause_loop() print('считаСм сколько Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚ΠΎΠ² ΠΏΠΎΠ»ΡƒΡ‡ΠΈΠ»ΠΈ:', len(list(iter(q.get, None)))) 

Sample output

 we start a flow
 start the cycle
 a piece of code that should be executed once each time the stream is 'turned on'
 code that runs until the restarted flag is reset
 suspend cycle
 consider how many results were received: 11
 start the cycle
 a piece of code that should be executed once each time the stream is 'turned on'
 code that runs until the restarted flag is reset
 suspend cycle
 we consider how many results received: 9

Remarks on the code:

  • use queue.Queue instead of multiprocessing.Queue (threads and processes are different things)
  • Do not use time.sleep() in code with many threads if you can find more explicit synchronization mechanisms between threads. For example, in the sample code, the response uses the blocking Queue.put() method . Thanks to Queue(maxsize=10) , the code in the background thread will pause if the main thread does not have time to remove the elements from the queue in time.
  • use command=start , instead of command=lambda: start() . lambda not needed in this case β€” in Python, functions can be passed as-is as parameters to other functions
  • in general, prefer delegation instead of inheritance. For example, tkinter.Tk() passed as a parameter to ShowProcessOutputDemo in the example code above, instead of using Tk as the base class: inheritance creates too close a link between the base and child classes (for example, you have to think about conflicts of non-private class attributes) β€”logics the application and logic of a reusable GUI element (window) may make sense to keep it separate.
  • Thank you for your participation. This is all still relevant for me, so thank you very much - Putch