1

Here is my problem: Using Tkinter, I want to click a button and launch a python script. This python script is now a module (I don't know if it s the best way to do it) imported to my main script. This script should be running in background. There is a method in it to quit it. How can I call it ? I wanted to pass a flag or something to the module but I don't know how to do that.

For now, I call my code like that: in gui.py

import sniffer_wideband_v09
from Tkinter import *
root = Tk()
def handle_click():
    global t
    global stop_flag
    stop_flag = 0

    def callback():
        sniffer_wideband_v09.main(sampling_rates, center_frequencies, gains, file_dir, files_names) 
    t = Thread(target=callback)
    t.start()
root.mainloop()

I would like to call the quitting() method in sniffer_wideband_v09.py. Or to pass a flag to my module to stop the infinite loop. Afterward, I will need to bind all of this to Tkinter Buttons.

I did a bit of research on the question and found :

Is there any way to kill a Thread in Python? along with How to run a function in the background of tkinter and How to run and stop an infinite loop in a python thread

The first is promising, but I don't fully understand it, I'm working on it.

Note: I run it directly from my shell with ./gui.py and I am under Ubuntu, not windows.(I think it can change some ways of dealing with multi threading).

Thanks for the reading, any hint or help will be appreciated. I will post my code if I find a response in the mean time.

Edit: To give more info about the script launched in the thread : (it is a GNURadio script)

class foo():
    def __init__(self):
         self.parameters()
    def methods():
         self.dostuff()
def main(sampling_rates, center_frequencies, gains, file_dir, files_names):
    tb = foo()
    while True: #flag could be here to exit the infinite while.
         tb.start()
    tb.stop()
def quitting():
    tb.stop()
 ## not mandatory piece of code from now on
if __name = "__main__":
     main()
     quitting()
Community
  • 1
  • 1
Paul
  • 13
  • 1
  • 5

3 Answers3

3

Since there is no interaction between your Tkinter gui application and the main code you're calling from the sniffer_wideband_09 module I would recommend you to use multiprocessing:

import sniffer_wideband_v09
import multiprocessing
from Tkinter import *
root = Tk()
def handle_click():
    global t

    t = multiprocessing.Process(target=sniffer_wideband_v09.main, args=(sampling_rates, center_frequencies, gains, file_dir, files_names))
    t.start()
root.mainloop()

When you want to stop this process just call t.terminate(). Read more on the multiprocessing module documentation.

mguijarr
  • 7,641
  • 6
  • 45
  • 72
  • Thanks, I will try this. For now, there are no interaction. Afterward, I would like the sniffer_wideband_v09 to be launched and stopped by 2 Tkinter buttons. – Paul Jul 29 '14 at 08:40
  • In fact you can even use multiprocessing if there is interaction, there is a queue mechanism to pass info from/to the subprocess and the parent one. – mguijarr Jul 29 '14 at 08:41
  • Indeed, this is properly working with my buttons. I will investigate the difference between threads and processes in python. I know those concepts, but it is the first time I use them in python code. I also need to see what really happens, and if "terminate" does not mess up the end of the script (as it is using a piece of special hardware and writting files) – Paul Jul 29 '14 at 08:49
  • From the docs: terminate sends the SIGTERM signal. You can set up a signal handler to do some cleanup, if needed. – mguijarr Jul 29 '14 at 11:51
  • @mguijarr When communicating between two processes, using a `multiprocessing.Pipe` is sufficient. A `Queue` has more overhead because it allows more than two processes to use it. – Roland Smith Jul 29 '14 at 15:21
1

GUI toolkits are generally event-driven. This means that the program runs in a loop processing events (mouse movements, clicks, keystrokes, timers et cetera).

There are generally three ways to have a long-running computation running in an event-driven program;

  • timer and callback
  • separate process
  • threads

The first solution is relatively simple. You set up a timer. This timer will produce an event or call a callback function (depends on the toolkit) after it runs out. You do a little bit of work in the callback, save the state of the computation, possibly update a progress indicator, restart the timer and exit the callback. This blends in well with the GUI but requires your code to be structured so that the work can be divided into small chunks. If the callbacks take too long, event processing will suffer and the GUI will feel unresponsive. The callback should run no longer than e.g. in between 10 and 100 ms. This method cannot take advantage of the multiple cores prevalent in modern CPUs.

The second solution is to start a separate process, as mguijarr demonstrated. On CPython this is a very good solution because you're this will not influence the GUI at all, and you can still communicate with the external program using all kinds of methods (signals, sockets, shared memory, message queues). This will take advantages of multiple cores. Using a multiprocessing.Pool you can even distribute work over all available cores.

Threads on the other hand are not an optimal solution in CPython in this case. The Global Interpreter Lock ("GIL") restricts the CPython interpreter so that only one thread at a time can execute Python bytecode. This was originally done to simplify memory management. So even if you are using a separate thread for computations, you can still starve the GUI of processor time and make in unresponsive. And you have to protect data used by multiple threads with locks. And with most GUI toolkits, only one thread can call toolkit functions. So you cannot e.g. update a progress indicator from a second thread.

Roland Smith
  • 42,427
  • 3
  • 64
  • 94
  • Thanks Roland for this answer. That is indeed what I understood from : http://stupidpythonideas.blogspot.it/2013/10/why-your-gui-app-freezes.html Callbacks can be a mess, thread aren't that suitable in python and processes can be great. I have to investigate a bit how to deal with my second process and I will close this issue. (just in case someone comes in with a great explanation / another idea. – Paul Jul 29 '14 at 12:17
  • @Paul If you use `multiprocessing.Process` to start a Python function in a separate process, you can use a `Pipe` to exchange messages. In you GUI you can generally use an `idle` callback to check if there is data in the pipe. – Roland Smith Jul 29 '14 at 15:17
0

I spent several hours trying to solve your problem for my own edification, using threading, and several unresponsive UIs later ended up with this: TkInter is not thread safe, from tkinter tkMessageBox not working in thread.

Community
  • 1
  • 1
moonbase3
  • 39
  • 3
  • Few (if any) GUI toolkits are completely thread-safe because that would require an unacceptable amount of overhead since almost every piece of shared data would need to be protected by a mutex. Several have the restriction that you can only invoke toolkit functions from one thread. – Roland Smith Jul 29 '14 at 15:12