5

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()
4
  • 1
    You didn't call endloop. Commented May 24, 2015 at 5:20
  • How are you calling both functions? Supposed to be in threads. Commented May 24, 2015 at 5:20
  • Sure but it' s actually in tkinter and a button it calling endloop() Commented May 24, 2015 at 5:21
  • 2
    You'll want to look into the tkinter event loop; your loop button isn't giving the endloop button a chance to run. See Why Your GUI App Freezes. Commented May 24, 2015 at 5:25

2 Answers 2

3

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for this answer strangely my main program does not make tkinter crash of anything but the endloop don't kill the loop. And actually the loop is counting time.
@ThierryLincoln: Please read more carefully. I did not say it will make Tkinter crash, and it won't. What it will do is make Tkinter stop responding to events. Which means your endloop function never gets called. Which is why it doesn't kill the loop.
1

Supposing your loop() function is doing some work in the background it would be a good idea to put that in a separate thread. Using threading Events you can interact with the thread.

This code is not tested but it should give you an idea how I have solved such stuff in some cases:

class Worker(threading.Thread):
    def __init__(self):
        threading.Thread.__init__()

        self.run_event = threading.Event()

    def run(self):
        self.run_event.wait()

        while self.run_event.is_set():
            print "It's doing something!"

You can then initialize the thread, it will call the run() method as soon as it is started and will wait in the first line for the run_event to be set.

So assuming you have a global variable that references your worker thread your loop() method triggered from the button would look like this:

def loop():
    global worker_thread
    worker_thread.run_event.set()

By setting the run_event the self.run_event.wait() gets passed and the work loop is entered. This while loop runs as long as the threading event is set.

And your endloop() may look something like this:

dev endloop():
    global worker_thread
    worker_thread.run_event.clear()

As soon as you clear the run_event in your thread, the while condition is no longer fulfilled and the loop quits.

Just a note: This code is not complete nor tested but may give you an idea how it could be done. Don't forget to

import threading

as well.

Hope this helps a little bit. Greetz

2 Comments

Thanks for replying actually I opened a thread yesterday about my critical error I'm really confused now and I need to give back this program tomorrow :( : stackoverflow.com/questions/30413689/…
Check the other stackoverflow question you linked here, I answered my ideas about it just a minute ago ;-)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.