Multiprocessing - tkinter pipeline communication - python-3.x

I have a question on multiprocessing and tkinter. I am having some problems getting my process to function parallel with the tkinter GUI. I have created a simple example to practice and have been reading up to understand the basics of multiprocessing. However when applying them to tkinter, only one process runs at the time. (Using Multiprocessing module for updating Tkinter GUI) Additionally, when I added the queue to communicate between processes, (How to use multiprocessing queue in Python?), the process won't even start.
Goal:
I would like to have one process that counts down and puts the values in the queue and one to update tkinter after 1 second and show me the values.
All advice is kindly appreciated
Kind regards,
S
EDIT: I want the data to be available when the after method is being called. So the problem is not with the after function, but with the method being called by the after function. It will take 0.5 second to complete the calculation each time. Consequently the GUI is unresponsive for half a second, each second.
EDIT2: Corrections were made to the code based on the feedback but this code is not running yet.
class Countdown():
"""Countdown prior to changing the settings of the flows"""
def __init__(self,q):
self.master = Tk()
self.label = Label(self.master, text="", width=10)
self.label.pack()
self.counting(q)
# Countdown()
def counting(self, q):
try:
self.i = q.get()
except:
self.label.after(1000, self.counting, q)
if int(self.i) <= 0:
print("Go")
self.master.destroy()
else:
self.label.configure(text="%d" % self.i)
print(i)
self.label.after(1000, self.counting, q)
def printX(q):
for i in range(10):
print("test")
q.put(9-i)
time.sleep(1)
return
if __name__ == '__main__':
q = multiprocessing.Queue()
n = multiprocessing.Process(name='Process2', target=printX, args = (q,))
n.start()
GUI = Countdown(q)
GUI.master.mainloop()

Multiprocessing does not function inside of the interactive Ipython notebook.
Multiprocessing working in Python but not in iPython As an alternative you can use spyder.

No code will run after you call mainloop until the window has been destroyed. You need to start your other process before you call mainloop.

You are calling wrong the after function. The second argument must be the name of the function to call, not a call to the function.
If you call it like
self.label.after(1000, self.counting(q))
It will call counting(q) and wait for a return value to assign as a function to call.
To assign a function with arguments the syntax is
self.label.after(1000, self.counting, q)
Also, start your second process before you create the window and call counting.
n = multiprocessing.Process(name='Process2', target=printX, args = (q,))
n.start()
GUI = Countdown(q)
GUI.master.mainloop()
Also you only need to call mainloop once. Either position you have works, but you just need one
Edit: Also you need to put (9-i) in the queue to make it count down.
q.put(9-i)
Inside the printX function

Related

How to stop/pause a worker thread, do an operation in the main thread and send the results to the worker thread in python?

I have a script that uses user input and based on that it generates some data (code below). The script uses threads so the GUI does not freeze while the background tasks are running.
My question is if it is possible to pause the worker thread, ask the user for some input, send it to the worker thread and then to continue the operation?
from tkinter.constants import LEFT, RIGHT, S
import tkinter.messagebox
import tkinter as tk, time, threading, random, queue
from tkinter import NORMAL, DISABLED, filedialog
class GuiPart(object):
def __init__(self, master, client_instance):
canvas = tk.Canvas(master, height=600, width=1020, bg="#263D42")
canvas.pack()
self.button1 = tk.Button(master, text="Mueller Matrix - MMP mode", padx=10,
pady=5, bd = 10, font=12, command=client_instance.start_thread1)
self.button1.pack(side = LEFT)
def files(self):
self.filena = filedialog.askopenfilenames(initialdir="/", title="Select File",
filetypes = (("comma separated file","*.csv"), ("all files", "*.*")))
return self.filena
def processing_done(self):
tkinter.messagebox.showinfo('Processing Done', 'Process Successfully Completed!')
return
class ThreadedClient(object):
def __init__(self, master):
self.running=True
self.gui = GuiPart(master, self)
def start_thread1(self):
thread1=threading.Thread(target=self.worker_thread1)
thread1.start()
def worker_thread1(self):
if self.running:
self.gui.button1.config(state=DISABLED) # can I disable the button from the main thread?
user_input = self.gui.files() # can I pause the Worker thread here so ask the user_input from main loop?
time.sleep(5) # to simulate a long-running process
self.gui.processing_done()
self.gui.button1.config(state=NORMAL)
root = tk.Tk()
root.title('test_GUI')
client = ThreadedClient(root)
root.mainloop()
I am asking this as from time to time my script ends abruptly saying that the GUI is not part of the main loop. Thank you for your help!
In general, you should run the GUI from the main thread.
There is some confusion whether tkinter is thread-safe. That is, if one can call tkinter functions and methods from any but the main thread. The documentation for Python 3 says “yes”. Comments in the C source code for tkinter say “its complicated” depending on how tcl is built.
You should be able to call tkinter functions and methods from additional threads in Python 3 if your tkinter was built with thread support. AFAIK, the tkinter that comes with the downloads from python.org have threading enabled.
If you want to be able to pause additional threads, you should create a global variable, e.g. pause = False.
The extra threads should regularly read the value of this and pause their calculations when set to true, e.g.:
def thread():
while run:
while pause:
time.sleep(0.2)
# do stuff
Meanwhile the GUI running in the main thread should be the only one to write the value of pause, e.g. in a widget callback.
Some sample callbacks:
def start_cb():
pause = False # So it doesn't pause straight away.
run = True
def stop_cb():
pause = False
run = False
def pause_cb():
pause = True
def continue_cb():
pause = False
If you have more complicated global data structures that can be modified be several threads, you should definitely guard access to those with a Lock.
Edit1
Threaded tkinter programs without classes:
Simple threaded tkinter example program
Real-world example for unlocking ms-excel files
Edit2:
From the python source code for _tkinter:
If Tcl is threaded, this approach won't work anymore. The Tcl
interpreter is only valid in the thread that created it, and all Tk
activity must happen in this thread, also. That means that the
mainloop must be invoked in the thread that created the
interpreter. Invoking commands from other threads is possible;
_tkinter will queue an event for the interpreter thread, which will
then execute the command and pass back the result. If the main
thread is not in the mainloop, and invoking commands causes an
exception; if the main loop is running but not processing events,
the command invocation will block.
You can read the whole comment here.

Invoke tkinter trace callback AFTER a certain variable delay has been achieved

I have a tkinter application that searches through a list of about 100000 wordlist when user types into the Entry widget (using trace with write callback to capture change in Entry variable).
I want to implement sort of a delay in order to NOT invoke the trace callback (to search the entire 100k wordlist) at EVERY keystroke (as the user might still be typing and it can become rather jerky/slow to invoke the callback function for each keystroke), rather I want to employ some sort of a min time to wait for additional input/keystroke AND/OR a max time since the first key was pressed BEFORE invoking the trace callback function.
I tried implementing a sleep but that is just a blocking call and does not achieve the desired affect. Here is some sample code where entering the string 'password' will invoke the callback (since this is literally just checking against the string 'password', it is super fast, yet in my app I loop over 100k word list for each keystroke which becomes slow). Thank You!
import tkinter as tk
from tkinter import ttk
class App(tk.Tk):
SUCCESS = 'Success.TLabel'
def __init__(self):
super().__init__()
self.title('Enter <password>')
self.geometry("200x120")
self.passwordVariable = tk.StringVar()
self.passwordVariable.trace('w', self.validate)
password_entry = ttk.Entry(
self, textvariable=self.passwordVariable) #, show='*'
password_entry.grid(column=0, row=1)
password_entry.focus()
self.message_label = ttk.Label(self)
self.message_label.grid(column=0, row=0)
def set_message(self, message, type=None):
self.message_label['text'] = message
if type:
self.message_label['style'] = type
def validate(self, *args):
confirm_password = self.passwordVariable.get()
if confirm_password == "password":
self.set_message(
"Success: The new password looks good!", self.SUCCESS)
return
if confirm_password.startswith("pas"):
self.set_message('Warning: Keep entering the password')
if __name__ == "__main__":
app = App()
app.mainloop()
I tried to understand what your current code does so I can implement the function here, but I've had no luck. Hopefully you being the author can implement this example onto your code.
The idea here is to schedule a callback to run after x seconds and if it is already scheduled, then cancel it. Sort of like a timer, if you think about it.
from tkinter import *
root = Tk()
SECONDS_TO_WAIT = 1
rep = None
def typing(*args):
global rep
if rep is None:
writing.config(text='Typing...')
else:
root.after_cancel(rep) # if already scheduled, then cancel it
rep = root.after(SECONDS_TO_WAIT*1000, caller)
def caller(*args):
global rep
writing.config(text='Not typing')
rep = None # Set it to None if `caller` GETS executed
var = StringVar()
entry = Entry(root,textvariable=var)
entry.pack(padx=10,pady=10)
entry.focus_force()
writing = Label(root,text='Not typing')
writing.pack()
var.trace('w',typing)
root.mainloop()
This will execute typing each time the entry widget is edited/written to. And according to the conditions inside the function, caller gets executed.

Tkinter only lets you use 1 button at a time?

so im making a gui miner for a coin called duinocoin, very new to tkinter.
So i integrated mining into a function, so when you press a button it mines. The problem is when I click mine I cant click anyother buttons because its in that mining loop. How do I fix this?
-Eth guy
Using threading, to create a separate thread from the UI thread:
from tkinter import *
from threading import Thread
from time import sleep
root = Tk()
def run(): #the loop function
b1.config(command=Thread(target=run).start) #or b1.config(state=DISABLED)
while True:
print('Hey')
sleep(2) #pause for 2 seconds.
def step(): #the in between function
print('This is being printed in between the other loop')
b1 = Button(root,text='Loop',command=Thread(target=run).start)
b1.pack()
b2 = Button(root,text='Separate function',command=step)
b2.pack()
root.mainloop()
Take a look at this example, first run the loop button, then press the next button and you will notice, it gets executed in between the while loop. But sleep() might still freeze your GUI out.
Explanation:
Threading is nothing much, just like a normal thread, imagine cars going through a thread and they got into accident, so the thread might break, just like that, your tkinter runs on one thread and your while loop runs with it causing the thread to freeze, but with threading you make a new thread for that function with while and hence the thread with tkinter is going smooth, while the thread with while loop is frozen, and it doesnt matter for the other thread.
Alternatively you could also use after() for this purpose, like:
from tkinter import *
from threading import Thread
from time import sleep
root = Tk()
def run():
global rep
print('Hey')
rep = root.after(2000,run) #run the same function every 2 seconds
def stop():
root.after_cancel(rep)
def step():
print('This is being printed in between the other loop')
b1 = Button(root,text='Loop',command=run)
b1.pack()
b2 = Button(root,text='Seperate function',command=step)
b2.pack()
b3 = Button(root,text='Stop loop',command=stop)
b3.pack()
root.mainloop()
Here when you press the loop button, it will start to loop, when you press the separate button, it prints a function in between the pseudo loop and when you press stop, it stops the loop.
after() method takes two arguments mainly:
ms - time to be run the function
func - the function to run after the given ms is finished.
after_cancel() takes the variable name of after() only.
Hopefully, you understood better, do let me know if you have any doubts or errors.
Cheers

Transfer of data between Python files

Need some help please to explain why the following does not work.
Environment: Python 3.4, Gtk3.0, limited experience of Python
File selectcontact.py contains code to select one of a number of records and pass its key back to its parent process for use in one of at least three other actions.
Code snippet from the parent class:
….
self.cindex = 0
….
def editcontact_clicked (self, menuitem):
import selectcontact
selectcontact.SelectContactGUI(self)
print ('From Manage ', self.cindex)
if self.cindex > 0:
import editcontact
editcontact.EditContactGUI(self.db, self.cindex)
….
Code snippet from selectcontact:
class SelectContactGUI:
def init(self, parent_class):
self.builder = Gtk.Builder()
self.builder.add_from_file(UI_FILE)
self.builder.connect_signals(self)
self.parent_class = parent_class
self.db = parent_class.db
self.cursor = self.db.cursor(cursor_factory = psycopg2.extras.NamedTupleCursor)
self.contact_store = self.builder.get_object('contact_store')
self.window = self.builder.get_object('window1')
self.window.show_all()
def select_contact_path(self, path):
self.builder.get_object('treeview_selection1').select_path(path)
def contact_treerow_changed (self, treeview):
selection = self.builder.get_object('treeview_selection1')
model, path = selection.get_selected()
if path != None:
self.parent_class.cindex = model[path][0]
print ('From select ', self.parent_class.cindex)
self.window.destroy()
….
window1 is declared as “modal”, so I was expecting the call to selectcontact to act as a subroutine, so that editcontact wouldn’t be called until control was passed back to the parent. The parent_class bit works because the contact_store is correctly populated. However the transfer back to the parent appears not to work, and the two print statements occur in the wrong order:
From Manage 0
From select 2
Comments gratefully received.
Graeme
"Modal" refers to windows only. That is, a modal window prevents accessing the parent window.
It has little to do with what code is running. I am not familiar with this particular windowing framework, but any I have worked with has had a separate thread for GUI and at least one for processing, to keep the GUI responsive, and message loops running in all active windows, not just the one currently with the focus. The modal dialog has no control over what code in other threads are executed when.
You should be able to break into the debugger and see what threads are running and what is running in each thread at any given time.

I'm new to coding. A while loop in Python won't work properly, but has no errors

I got this code from a book called "Python for Kids," by Jason Briggs. This code was ran in Python 3.4.3. I don't have any outside coding experience outside from this book. I tried multiple possibilities to fix this and looked online, but I couldn't find anything that would work or help my problem. If you have new code or edits for this code, that would be helpful to me continuing to learn Python.
from tkinter import *
import random
import time
class Game:
def __init__(self):
self.tk = Tk()
self.tk.title("Mr. Stick Man Races for The Exit")
self.tk_resizable(0, 0)
self.tk.wm_attributes("-topmost", 1)
self.canvas = Canvas(self.tk, width=500, height=500, highlightthickness=0)
self.canvas.pack()
self.tk.update()
self.canvas_height = 500
self.canvas_width = 500
self.bg = PhotoImage(file="Wallpaper.gif")
w = self.bg.width()
h = self.bg.height()
for x in range(0, 5):
for y in range(0, 5):
self.canvas.create_image(x * w, y * h, image=self.bg, anchor='nw')
self.sprites = []
self.running = True
def mainloop(self):
while 1:
if self.running == True:
for sprite in self.sprites:
sprites.move()
self.tk.update_idletasks()
self.tk.update()
time.sleep(0.01)
g = Game()
g.mainloop()
This code was supposed to make a window with a wallpaper I created in Gimp to fill the window. When I ran the code, nothing happened and no errors appeared. What I need help on is making a window with my wallpaper appear. If you can help, can you give me an explanation with code. I'm sorry if my mistakes are obvious.
These two statements need to be all the way to the left, with no indentation:
g = Game()
g.mainloop()
The code class Game: creates a class, which can be thought of as a recipe for how to create a game. It does not actually create the game, it only provides code to create the game.
In order to actually create the game -- called instantiatiation -- you need to call Game as if it was a function. When you do g = Game() you are actually creating the game object and saving a reference to it. Unless you do this, the game will never be created. Thus, to create a instance of the game, you must define it in one step (class Game()) and create it in another (g = Game())
Warning, there are other problems in the code. This answers your specific question, but you need to fix the indentation of the def mainloop statemen, and there may be other issues.
The biggest problem is that this simply isn't the right way to do animation in Tkinter. It might be ok as a learning tool, but ultimately this is simply not proper usage of tkinter. I don't know if this code is straight from the book or if this is code you're trying on your own, but tkinter simply isn't designed to work this way. You shouldn't be creating your own mainloop function because tkinter has one that is built in.
To fix the mainloop issue, remove the existing mainloop function, and add this in its place (properly indented under class Game():
def mainloop(self):
# start the animation
self.animate()
# start the event loop
self.tk.mainloop()
def animate(self):
if self.running == True:
for sprite in self.sprites:
sprites.move()
self.tk.after(10, self.animate)

Resources