Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

End a loop in a function from another function in python

Is is possible in python to end a loop in a function from another function? This does not seem to work

Here is my code:

from tkinter import*
root = Tk()
def loop():
    global superman
    superman=False
    while superman==False:
        print("It's doing something")
def endloop():
    global superman
    superman=True

btn_1 = Button(root, text="stop", command=endloop)
btn_1.pack()
btn_2 = Button(root, text="start", command=loop)
btn_2.pack()
like image 771
Thierry Lincoln Avatar asked Sep 28 '22 20:09

Thierry Lincoln


1 Answers

The problem here is that your while loop just keeps running, meaning none of the rest of your code ever gets to run. That includes the Tkinter GUI, which means that your program doesn't respond to any user events, including the button click, so endloop never gets called.

More generally, you really can't have a function that just runs forever, or even for more than a fraction of a second, inside a GUI program. A single-threaded program can only do one thing at a time; if what it's doing is looping forever, then it's not doing anything else.

So, what can you do?

There are two basic options:

  1. Put the loop on a background thread. This means that any shared data now has to be explicitly synchronized, and it means the loop can't touch any of the GUI widgets—but in your case, that turns out to be pretty simple.

  2. Break up the loop. Have it just do one iteration (or, say, 100 iterations, if they're really quick), and then use after or after_idle to ask Tkinter to call a function that does another one iteration (or 100 iterations) and afters again, and so on, until they're all done.

I'll show you how to do the first one here.

import threading
from tkinter import*
root = Tk()

def real_loop():
    while True:
        with superman_lock:
            if not superman:
                return
       print("It's doing something")
def loop():
    global superman
    global superman_lock
    superman=False
    superman_lock = threading.Lock()
    thread = threading.Thread(target=real_loop, daemon=True)
def endloop():
    global superman
    with superman_lock:
        superman=True

btn_1 = Button(root, text="stop", command=endloop)
btn_1.pack()
btn_2 = Button(root, text="start", command=loop)
btn_2.pack()

For the case where the only shared data is a "stop" flag, a Condition or Event is often better than a Lock. The threading docs explain the differences between the different kinds of sync objects, but not really at an introductory level. The Wikipedia article on monitors might be a better starting point to learn, but if you can find a good tutorial on multithreading (not necessarily Python-specific; Python has basically the same sync objects as the C pthreads library, the C++ Boost library, the Java stdlib, etc.), that would probably be better.

For a much more detailed discussion, see Why your GUI app freezes.

like image 58
abarnert Avatar answered Oct 01 '22 04:10

abarnert