Search code examples
pythonuser-interfacetkinterpython-multiprocessing

GUI triggering actions in a separate process


I'm experiencing a problem with python-multiprocessing and Tkinter GUI. I have two processes:

  • main, running Tkinter GUI in a loop
  • second process to do all other tasks

Here are the things I'm struggling with:

  1. I want to trigger an action (f.eg. start a specific task in the second process) by clicking on a GUI button.
  2. Then, when user clicks that button and the task in the second process starts, GUI changes the window to a new one (already know how to change windows). In the new one there is also a bunch of buttons, but I want them to be unclickable (or cause nothing when clicked) until the task in 2nd process is finished.

What is the best way to do this?

EDIT: Before I started to use multiprocessing, the code for my button and the triggered function looked like this:

button48g = tk.Button(self, text='48g', height = 10, width = 20, font=("Courier", 20), bg="WHITE", command=lambda: controller.show_frame(GUI.PageOne48g))
button48g.bind('<Button-1>', GUI.commands.leftClick)

where leftClick was a function, f.eg.

def leftClick(event):
    print('Left click!')

Now, the leftClick function is in the other process, called 'test'.

queueTestResults = multiprocessing.Queue()
test = multiprocessing.Process(name='test', target=testProcess, args=(queueTestResults,))
test.start()

gui = GUI.GuiApp()
gui.root.mainloop()

while queueTestResults.empty():
    pass

someResults = queueTestResults.get()
test.join()

Solution

  • Its easier to accomplish with 2 Queues rather than 1 queue in my opinion

    q_read,q_write = multiprocessing.Queue(),multiprocessing.Queue()
    
    def slow_function():
        time.sleep(5)
        return "Done Sleeping"
    
    def other_process_mainloop(q_write,q_read):
        # this process must get an inverted order
        while 1: # run forever
           while q_read.empty():
               time.sleep(0.1) # waiting for a message
           message = q_read.get()
           if message == "command":
               q_write.put(slow_function())
           else:
               q_write.put("Unknown Command")
    
    # start our worker bee
    P = multiprocessing.Process(name="worker",target=other_process_mainloop,
                                        # note inverted order
                                kwargs={q_write:q_read,q_read:q_write)
    

    now make a helper function to send the command and wait for the response

    def send_command(command_string,callback_fn):
        def check_for_response():
           if q_read.empty():
              return gui.after(200, check_for_response)
           callback_fn(q_read.get())
    
        q_write.put(command)
        gui.after(200,check_for_response)
    
    # now you can just call this method when you want to send a command
    
    def on_button_clicked(event):
        def on_result(result):
            print("Work is Done:",result)
            gui.lbl.configure(text="All Done!! {}".format(result))            
    
        gui.lbl.configure(text="Doing some work mate!")
        send_command("command",on_result)