How to terminate two process with tkinter button in Python - python

I want to create two processes with python script. There is need to start and stop those processes via tkinter button. Processes start correctly, but terminate not. Which simplest way to correctly terminate processes with tkinter button?
Which best-practice way?
from tkinter import *
import multiprocessing
def print1():
global a
while a == True:
print('im process 1')
def print2():
global a
while a == True:
print('im process 2')
def start():
process1.start()
process2.start()
def stop():
global a
a = False
a = True
if __name__ == '__main__':
process1 = multiprocessing.Process(target = print1)
process2 = multiprocessing.Process(target = print2)
root = Tk()
root.title("Title")
root.geometry("200x200")
app = Frame(root)
app.grid()
start = Button(app, text="Start", command=start)
stop = Button(app, text="Stop", command=stop)
start.grid()
stop.grid()
root.mainloop()
process1.join()
process2.join()

The problem appears to be in the stop method.
I think I might know what the problem is, but I'm not 100% sure. The answer to why appears to be in the python documentation. This code here runs fine (I edited the syntax and used tkk for the buttons, it looks better):
from tkinter import *
from tkinter import ttk
import multiprocessing
def print1():
global a
while a is True:
print('im process 1')
def print2():
global a
while a is True:
print('im process 2')
def start():
process1.start()
process2.start()
def stop():
process1.kill()
process2.kill()
a = True
if __name__ == '__main__':
process1 = multiprocessing.Process(target=print1)
process2 = multiprocessing.Process(target=print2)
root = Tk()
root.title("Title")
root.geometry("200x200")
app = Frame(root)
app.grid()
start = ttk.Button(app, text="Start", command=start)
stop = ttk.Button(app, text="Stop", command=stop)
start.grid(padx=15, pady=20)
stop.grid(column=1, row=0)
root.mainloop()
process1.join()
process2.join()
Hope this helps!

Ordinary variables are not shared between instances of a multiprocessing.Process.
This means your:
global a
is a different global variable in each process, separate from the third global a in your main Python program. So when you use the tk button to set that last a to False, the a in the process remembered through process1 is still True, as is the a in the process remembered through process2.
You can share variables across processes. There are two ways to do that: via Manager instances, or via shared memory. Shared memory is more efficient, but more difficult to use, and sometimes has OS dependencies, so if you don't need particularly high performance, consider using a Manager.

Related

How to destroy tkiner window from another thread

I m sorry if it's a little confusing I will try to explain as simple as possible way, I have been trying to solve this threading issue for the past 2 days.
In short, what I m doing is running a big script on a separate thread once I hit the run button from the first screen, it reads data from the JSON file I have stored in the directory and Run a job.
Which gradually after completing steps increases the progress bar value and updates the label status from the run_script function.
And the error is after completing the first cycle when it comes back to first screen and if I run another job it doesnt work because of progressbar and label.
What I've tried so far is destroying the window from script_function and recreating it on the first screen. (2nd job worked but it didn't destroy the first job window, and after completing 2nd job it completely shutdown both windows)
without mttkinter, I got tcl run time error because I m trying to destroy the window from another thread.
from mttkinter import mtTkinter as tk
from tkinter import ttk
import threading
#other imports
window = Tk()
def screen1():
##show the screen with json data labels and buttons#
window.geometry("1215x770")
window.configure(bg="#FFFFFF")
b0 = Button(window, text='Run job', command=do_something) #samplebutton
window.resizable(False, False)
window.mainloop()
def do_something():
##running big script with progressbar and status label.
window.geometry("1215x770")
window.configure(bg="#FFFFFF")
progress = ttk.Progressbar(window, style='red.Horizontal.TProgressbar', orient=HORIZONTAL,
length=443, mode='determinate')
progress.pack()
label = Label("Completed tables 0/{totaltables}") #samplelabel
label.pack()
if data['foo'] == 'bar':
threading.Thread(target=run_script, args=(arg1, arg2,)).start() #run script in seperate thread this script also has a function that runs on multiple threads.
window.resizable(False, False)
window.mainloop()
def run_script(arg1, arg2):
#running the script#
for i in range(5): #running each i on new thread followed by starting and joining.
print(i)
#main problem#
#once script finishes#
response = messagebox.showinfo("Task", "Did something!")
if response:
screen1()
What is the possible way to accomplish this?
I want to keep running jobs without having to open the app again and again.
the script should be on a separate thread because if it's not the app goes not responding.
I want to keep showing the progress bar and status and once it's complete go back to the first screen.
How do I kill the main Tkinter thread after completing the first job?
or How do I destroy the Tkinter window from another thread?
If you have a tcl/tk that is built with multithreading support, and you are using Python 3, you should be able to call tk functions from a second thread.
This is an example program, where a second thread modifies widgets:
import os
import sys
import threading
import time
import tkinter as tk
import tkinter.font as tkfont
import types
__version__ = "2022.02.02"
# Namespace for widgets that need to be accessed by callbacks.
widgets = types.SimpleNamespace()
# State that needs to be accessed by callbacks.
state = types.SimpleNamespace()
def create_widgets(root, w):
"""
Create the window and its widgets.
Arguments:
root: the root window.
w: SimpleNamespace to store widgets.
"""
# General commands and bindings
root.wm_title("tkinter threading v" + __version__)
root.columnconfigure(2, weight=1)
root.rowconfigure(3, weight=1)
root.resizable(False, False)
# First row
tk.Label(root, text="Thread status: ").grid(row=0, column=0, sticky="ew")
w.runstatus = tk.Label(root, text="not running", width=12)
w.runstatus.grid(row=0, column=1, sticky="ew")
# Second row
tk.Label(root, text="Timer: ").grid(row=1, column=0, sticky="ew")
w.counter = tk.Label(root, text="0 s")
w.counter.grid(row=1, column=1, sticky="ew")
# Third row
w.gobtn = tk.Button(root, text="Go", command=do_start)
w.gobtn.grid(row=2, column=0, sticky="ew")
w.stopbtn = tk.Button(root, text="Stop", command=do_stop, state=tk.DISABLED)
w.stopbtn.grid(row=2, column=1, sticky="ew")
def initialize_state(s):
"""
Initialize the global state.
Arguments:
s: SimpleNamespace to store application state.
"""
s.worker = None
s.run = False
s.counter = 0
def worker():
"""
Function that is run in a separate thread.
This function *does* update tkinter widgets. In Python 3, this should be
safe if a tkinter is used that is built with threads.
"""
# Initialization
widgets.runstatus["text"] = "running"
# Work
while state.run:
time.sleep(0.25)
state.counter += 0.25
widgets.counter["text"] = f"{state.counter:.2f} s"
# Finalization
state.counter = 0.0
widgets.counter["text"] = f"{state.counter:g} s"
widgets.runstatus["text"] = "not running"
def do_start():
"""Callback for the “Go” button"""
widgets.gobtn["state"] = tk.DISABLED
widgets.stopbtn["state"] = tk.NORMAL
state.run = True
state.worker = threading.Thread(target=worker)
state.worker.start()
def do_stop():
"""Callback for the “Stop” button"""
widgets.gobtn["state"] = tk.NORMAL
widgets.stopbtn["state"] = tk.DISABLED
state.run = False
state.worker = None
# Main program starts here.
if __name__ == "__main__":
# Detach from the command line on UNIX systems.
if os.name == "posix":
if os.fork():
sys.exit()
# Initialize global state
initialize_state(state)
# Create the GUI window.
root = tk.Tk(None)
# Set the font
default_font = tkfont.nametofont("TkDefaultFont")
default_font.configure(size=12)
root.option_add("*Font", default_font)
create_widgets(root, widgets)
root.mainloop()
Note how the worker thread monitors state.run and exits when it becomes False.
In general I find using multiple top-level windows confusing, so I don't use that pattern.
I would rather create a grid with rows of a progressbar and stop button for every thread.

Interrupting a script called within another script in tkinter

I have a program (say p1.py) which calls another python script (say p2.py) on click of a button. I would like to have a button which stops the execution of the p2.py but all the buttons freeze when it is running.
The only way to stop it is to use a keyboard interrupt in the console. I have read about the after() function but do I have to implement it in p1 or p2? Or is there any other way to do it without the after() function?
import tkinter
import os
window = tkinter.Tk()
window.title("Detecting")
def clicked():
os.system('python extract_frames.py')
bt = tkinter.Button(window,text="Start",command=clicked)
bt.pack()
stop = tkinter.Button(window,text="Stop",command="break") #also what command should I use for the interrupt?
stop.pack()
window.geometry('400x400')
window.mainloop()
You should use subprocess.Popen() instead of os.system():
import tkinter
import subprocess
proc = None
def clicked():
global proc
proc = subprocess.Popen(['python', 'extract_frames.py'])
def kill_task():
if proc and proc.poll() is None:
print('killing process ...')
proc.kill()
window = tkinter.Tk()
window.geometry('400x400')
window.title("Detecting")
bt = tkinter.Button(window, text="Start", command=clicked)
bt.pack()
stop = tkinter.Button(window, text="Stop", command=kill_task)
stop.pack()
window.mainloop()

Tkinter is opening new windows when running a function in a thread

Hi all I am using python 2.7.15 and tkinter. It is a simple GUI with some buttons. Once a button is pressed I need to start a function in a thread (I do not need to open any new windows).
What is happening is for each thread a new GUI copy of the program is opened. Is there any way to start a function (that does some calculations) without popping up a new copy of the Tkinter gui?
I am making a thread like this:
thread = Process(target=functionName, args=(arg1, arg2))
thread.start()
thread.join()
EDIT: here is some code to reproduce. As you can see, all I am interested in below "sample" is to run one function. Not to clone the whole program.
from Tkinter import *
from multiprocessing import Process
window = Tk()
window.title("Test threadinng")
window.geometry('400x400')
def threadFunction():
sys.exit()
def start():
thread1 = Process(target=threadFunction)
thread2 = Process(target=threadFunction)
thread1.start()
thread2.start()
thread1.join()
thread2.join()
btn = Button(window, text="Click Me", command=start, args=())
btn.grid(column=1, row=1)
window.mainloop()
Thank you.
Since the child process will inherit resource from parent process, that means it will inherit tkinter from parent process. Put the initialization of tkinter inside if __name__ == '__main__' block may solve the problem:
from tkinter import *
from multiprocessing import Process
import time
def threadFunction():
print('started')
time.sleep(5)
print('done')
def start():
thread1 = Process(target=threadFunction)
thread2 = Process(target=threadFunction)
thread1.start()
thread2.start()
thread1.join()
thread2.join()
if __name__ == '__main__':
window = Tk()
window.title("Test threadinng")
window.geometry('400x400')
btn = Button(window, text="Click Me", command=start)
btn.grid(column=1, row=1)
window.mainloop()

Python multiprocessing and tkinter - how to connect this process (GUI and spawned process)?

long time ago I started to use threading. Thread for any functions in my GUI. But last time I focused on multiprocessing module. The problem here is that when I spawn new process I don't know how to connect with my GUI.
Here is sample code - one button "execute" Thread and it works as expected.
Second button spawn Process but my GUI is not updated (input field is not filled with text).
How to fix that?
How to receive "print" statements from Process?
Thanks!
import tkinter as tk
import time
from multiprocessing import Process, Pool
from threading import Thread
root = tk.Tk()
inputEn = tk.Entry(root)
inputEn.pack()
def runner(txt):
print("runner")
time.sleep(5)
# this doesn't work when I use it with Process
inputEn.insert(0, "{}".format(txt))
print("runner end")
def process():
p = Process(target=runner, args=("process",))
p.start()
def thread():
p = Thread(target=runner, args=("thread",))
p.start()
btnStart = tk.Button(root, text="Start Process", command=process)
btnStart.pack()
btnStart2 = tk.Button(root, text="Start Thread", command=thread)
btnStart2.pack()
if __name__=="__main__":
root.mainloop()
When spawned using process, the child process cannot update the variable of the parent process.
You can experiment by modifying your code :
inputEn.pack()
globalv = 0
def runner(txt):
global globalv
print("runner")
time.sleep(5)
# this doesn't work when I use it with Process
inputEn.insert(0, "{}".format(txt))
globalv = globalv + 1
print 'globalv : ', globalv
globalv will then be updated when runner is called via thread, by not updated when runner is called via process.
You would have to use queue to make child interact with your application.
This is because a child process is instanciated using a copy of the context of the parent process - whereas a thread uses the same context as the parent.
You have not said why multiprocessing is necessary here. If you just want to update a variable, tkinter's after() works well for that. A not so elegant example
import Tkinter as tk
import random
class UpdateLabel(object):
def __init__(self, master):
self.master=master
self.str_var=tk.StringVar()
self.str_var.set("start")
tk.Entry(root, textvariable=self.str_var, width=50).pack()
tk.Button(self.master, text="Exit", bg="orange",
command=self.master.quit).pack()
self.ctr=0
self.change_it()
def change_it(self):
""" use random to simulate getting some kind of data 10 times
"""
this_lit=self.str_var.get()
next_lit=random.choice(range(100))
self.str_var.set(this_lit+", "+str(next_lit))
self.ctr += 1
if self.ctr < 10:
self.master.after(500, self.change_it)
root = tk.Tk()
UL=UpdateLabel(root)
root.mainloop()

Threads and tkinter

I've heard that threads in Python are not easy to handle and they become more tangled with tkinter.
I have the following problem. I have two classes, one for the GUI and another for an infinite process. First, I start the GUI class and then the infinite process' class. I want that when you close the GUI, it also finishes the infinite process and the program ends.
A simplified version of the code is the following:
import time, threading
from tkinter import *
from tkinter import messagebox
finish = False
class tkinterGUI(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
global finish
#Main Window
self.mainWindow = Tk()
self.mainWindow.geometry("200x200")
self.mainWindow.title("My GUI Title")
#Label
lbCommand = Label(self.mainWindow, text="Hello world", font=("Courier New", 16)).place(x=20, y=20)
#Start
self.mainWindow.mainloop()
#When the GUI is closed we set finish to "True"
finish = True
class InfiniteProcess(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
global finish
while not finish:
print("Infinite Loop")
time.sleep(3)
GUI = tkinterGUI()
GUI.start()
Process = InfiniteProcess()
Process.start()
When I click in the close button (in the upper right corner) the following error appears in the console:
Tcl_AsyncDelete: async handler deleted by the wrong thread
I don't know why it happens or what it means.
All Tcl commands need to originate from the same thread. Due to tkinter's
dependence on Tcl, it's generally necessary to make all tkinter gui statements
originate from the same thread. The problem occurs because
mainWindow is instantiated in the tkinterGui thread, but -- because mainWindow is an attribute of tkinterGui -- is not destroyed until tkinterGui is destroyed in the main thread.
The problem can be avoided by not making mainWindow an attribute of tkinterGui
-- i.e. changing self.mainWindow to mainWindow. This allows mainWindow to be destroyed when the run method ends in the tkinterGui thread. However, often you can avoid threads entirely by using mainWindow.after calls instead:
import time, threading
from tkinter import *
from tkinter import messagebox
def infinite_process():
print("Infinite Loop")
mainWindow.after(3000, infinite_process)
mainWindow = Tk()
mainWindow.geometry("200x200")
mainWindow.title("My GUI Title")
lbCommand = Label(mainWindow, text="Hello world", font=("Courier New", 16)).place(x=20, y=20)
mainWindow.after(3000, infinite_process)
mainWindow.mainloop()
If you want to define the GUI inside a class, you can still do so:
import time, threading
from tkinter import *
from tkinter import messagebox
class App(object):
def __init__(self, master):
master.geometry("200x200")
master.title("My GUI Title")
lbCommand = Label(master, text="Hello world",
font=("Courier New", 16)).place(x=20, y=20)
def tkinterGui():
global finish
mainWindow = Tk()
app = App(mainWindow)
mainWindow.mainloop()
#When the GUI is closed we set finish to "True"
finish = True
def InfiniteProcess():
while not finish:
print("Infinite Loop")
time.sleep(3)
finish = False
GUI = threading.Thread(target=tkinterGui)
GUI.start()
Process = threading.Thread(target=InfiniteProcess)
Process.start()
GUI.join()
Process.join()
or even simpler, just use the main thread to run the GUI mainloop:
import time, threading
from tkinter import *
from tkinter import messagebox
class App(object):
def __init__(self, master):
master.geometry("200x200")
master.title("My GUI Title")
lbCommand = Label(master, text="Hello world",
font=("Courier New", 16)).place(x=20, y=20)
def InfiniteProcess():
while not finish:
print("Infinite Loop")
time.sleep(3)
finish = False
Process = threading.Thread(target=InfiniteProcess)
Process.start()
mainWindow = Tk()
app = App(mainWindow)
mainWindow.mainloop()
#When the GUI is closed we set finish to "True"
finish = True
Process.join()
The fix here is simple, but hard to discover:
Call mainWindow.quit() immediately after mainwindow.mainloop(), so that the cleanup happens on the same thread as the one that created the tk UI, rather than on the main thread when python exits.

Categories