0

I have a module that calls another module where I initialize a canvas with tkinter and a button. When the user clicks on the button it launches a function from the first module. However this function is long and I don't want to have tkinter frozen until the function is completely executed. It seems that multiprocessing would be the solution but I have some difficulties implementing it.

In the first module:

tkinterModule.initialize(functionFromMainModule)

In the second module:

...
button = Button(master, 
                 text="Launch Function", 
                 command=partial(play, callback))

def play(callback=None):
     if callback is not None:
         callback()

Then in the first module:

def functionFromMainModule():
    ....
    if __name__ == '__main__':
        p = multiprocessing.Process(target=longFunction)
        p.start()

def longFunction():
    ...

But instead of launching longFunction it just reinitializes a new tkinter canvas and doesn't start the function. If I just call the function and don't use multiprocessing the function is called normally (but tkinter is frozen until the end of execution).

tienow
  • 171
  • 12
  • all GUI should be in one thread. Other threads or processes should only make calculations and send result or information to main thread which will change GUI. – furas Apr 30 '19 at 23:18
  • maybe create minimal working example with your problem so we could run it. – furas Apr 30 '19 at 23:22
  • maybe you should run all code in `if __name__ == '__main__':`, not only multiprocess. – furas Apr 30 '19 at 23:33
  • Are you on windows? If yes, you should use `if __name__ == '__main__'` statement – sakost May 01 '19 at 00:26

2 Answers2

1

Well I suppose, that you are on windows, so your problem is when a new process starts, the Python is pickle-ing all code in your module(or package) (it's because of windows restrictions)

So, when you use a multiprocessing module, you should use a if __name__ == '__main__': statement. Here is the explanation why:

When your code has pickled, it runs in another process, and there all is the same to main process, excluding __name__ variable. This variable can save the code from running the program as the main. Because of this bug, your program clears the canvas and then starts working from scratch.

sakost
  • 250
  • 4
  • 15
  • That works! I'm just surprised the whole code in the module is re-run when you actually specify a function in the process: `p = multiprocessing.Process(target=longFunction)` – tienow May 01 '19 at 17:30
0

A possible solution to this problem would be using threads. Python has a module called threading. You can use this module to create threads so that your tkinter app wont freeze. Here is what it looks like.

import threading

def Function(Data):
   print(Data)

thread = threading.Thread(target=Function, args=["Hi"], daemon=True).start()

# daemon simply means that the thread will die once done executing.

We create a thread called thread. The thread will execute the function. Threads divide the program into simultaneously running tasks. Which in other words means that two or more tasks can be ran at a similar time. Threads in the same process use the same memory and resources while a process does not. You can also create threads in classes using the threading module. Just import the threading module and inherit it in a class. For instance,

import threading

class Animal(threading.Thread):
    def __init__(self, v):

        threading.Thread.__init__(self, Variable):  # init for threaded classes
        self.Variable = Variable
        self.Run()
    def Run(self):
        print("I am an animal")


A = Animal("Snake")

A.start()

This is a great thing if you want an object to be acting without slowing down your app and making it crash and freeze.

  • This is not how it’s done. The one thing that one is not to do is to CALL the target, as you do. Instead one is only allowed to pass a callable and additional arguments either as args argument or via functools.partial. – deets May 01 '19 at 00:25
  • Also im sorry to say but your class based example is not working either. Case matters. You need to call Run ˋrunˋ instead, but more importantly START the thread. And call ˋThread.__init__ˋ. – deets May 01 '19 at 00:27
  • Thanks for correcting what I said. I made a mistake writing. – Darren Samora May 01 '19 at 01:04
  • Why call it run – Darren Samora May 01 '19 at 01:41
  • Deets. If we cant call the target then how can we execute the thread? – Darren Samora May 01 '19 at 01:42
  • In case of subclassing Thread, run is the method that will be invoked in it’s on Thread. So that’s why it has to be called that way. And for BOTH scenarios, you need to call start to actually start the thread. – deets May 01 '19 at 09:07
  • I actually tried to use processes because I need to be able to stop the execution of the function when I click on another button, I was told that it's not possible (or difficult) to do that with threads? – tienow May 01 '19 at 17:07
  • A thread can be flagged as a "daemon thread". The significance of this flag is that the entire Python program exits when only daemon threads are left. The initial value is inherited from the creating thread. – Darren Samora May 02 '19 at 06:06
  • In the function. You can return a value that you don't need to use. And that'll hopefully stop the thread from executing the function over and over again depending on if you have a loop. Or you can just use the break keyword to stop loop. – Darren Samora May 02 '19 at 06:12
  • The daemon tagged thread gets killed once the application is done running. Deets. – Darren Samora May 02 '19 at 06:36