user808996
user808996

Reputation: 179

Threading with Tkinter

I need to be able to send control-c to the new thread that I start. How would I go about this. I can start the thread, but I need to stop like I do in the command line using control-c.

from Tkinter import *
import threading # should use the threading module instead!
import Queue
import os


def show1():
    os.system('ola_recorder -p MyRecord -i 0')

def show2 ():
    os.system('ola_recorder -p MyRecord ')

def stop ():
    os.system('^c')


t = threading.Thread(name='Show2', target=show2)

root = Tk()
b = Button(root, text="Show 1", command=lambda: thread.start_new(show1, ()))
b.pack()

b2 = Button(root, text="Show 2", command=lambda: t.start())
b2.pack()

root.mainloop()

Upvotes: 1

Views: 1724

Answers (1)

abarnert
abarnert

Reputation: 365597

First, the reason what you're doing doesn't work:

Each call to os.system just calls your platform's system (or _system, on some Windows platforms) function, which creates a brand-new shell process each time. So, there's no way you can do anything with os.system to affect another call.


If you want to send a ^C to an arbitrary process, you can do that with os.kill. To make it portable, you have to do something like this:

def send_ctrl_c(pid):
    try:
        sig = signal.CTRL_C_EVENT
    except AttributeError:
        sig = signal.SIGINT
    else:
        os.signal(pid, sig)

However, you need the other process's pid to do that, and os.system doesn't give you that.


So, the right thing to do is to use the subprocess module:

proc2 = None

def show2():
    global proc2
    proc2 = subprocess.Popen('ola_recorder -p MyRecord', shell=True)
    proc2.wait()

Since there's no good reason to use the shell here, you'd probably be better off passing Popen a list of args, instead of a string, and leaving off the shell=True. And of course it would be a lot cleaner to not stick proc2 in a global, but I'll ignore that for this toy example.

Anyway, now you can get the pid and use it:

def stop():
    send_ctrl_c(proc2.pid)

However, if you're going to do that, you might as well just use the Popen object directly. See the docs for full details of what you can do with it, but here's a quick version:

def stop():
    global proc2
    try:
        sig = signal.CTRL_C_EVENT
    except AttributeError:
        sig = signal.SIGINT
    proc.send_signal(sig)

When you call stop, the process will get killed exactly as if it had received a ^C (POSIX), or as close as possible to as if it had received a ^C (Windows), the wait call will return (with a -2, at least on POSIX), and your thread will finish as well.


One last note: You almost never want to use the thread module directly, and you do not want to reuse threading.Thread objects. So, instead of this:

b = Button(root, text="Show 1", command=lambda: thread.start_new(show1, ()))

… or this:

b2 = Button(root, text="Show 2", command=lambda: t.start())

… do this:

def start2():
    global t2
    t2 = threading.Thread(name='Show2', target=show2)
    t2.start()

Of course, a non-toy program should avoid using a global, and will want to keep track of all existing threads instead of just the last one (if you're allowed to click the button while a background thread is already running), and will probably want to join all of its threads at quit.

Upvotes: 6

Related Questions