Search code examples
pythonkivykivy-language

Calling sequential Threads for Kivy app with Progress Bar


I'm writing a Kivy app in which a user provides various input then clicks a button which is supposed to a call a selection of python functions which run in sequential background threads while a progress bar is updated in the GUI. I have three functions which are supposed to be called one after another, after the previous one has completed and the progress bar is at 100% the bar goes back to 0% and reports the progress of the next function etc. The problem is, if I call them in order, it looks like they're being run simultaneousy in the background with only the last one updating the progress bar. How do I setup threads to be called only after the previous one has finished?

I've tried the join method but it just freezes the GUI by putting the thread on the main thread.

This is the current function which is called when the button's pressed:

scripts_to_run = ListProperty([])

def process_data(self):
    to_run = []
    if "move_data" in self.scripts_to_run:
        self.move_data()
        to_run.append(Thread(target=self.produce_data_file))
    if "histograms" in self.scripts_to_run:
        to_run.append(Thread(target=self.histograms))
    if "log_histograms" in self.scripts_to_run:
        to_run.append(Thread(target=self.log_histograms))

    for thread in to_run:
        thread.start()

Solution

  • So, with some help from @TwfyqBhyry, I managed to put a solution together which works but there's probably a slicker way of doing it.

    Since while loops don't agree with Kivy, I used what I think of as the Kivy alternative to a while loop, Clock.schedule_interval, to repeatedly call a function which checks if a thread is alive and calls the next thread when the previous one is no longer alive.

    scripts_to_run = ListProperty([])
    
    def process_data(self):
        for script in self.scripts_to_run:
            if script == "move_data":
                self.move_data()
                move_data_thread = Thread(target=self.produce_data_file)
                move_data_thread.start()
            if script == "histograms":
                histo_thread = Thread(target=self.histograms)
                if move_data_thread.is_alive():
                    self.event = Clock.schedule_interval(partial(self.check_for_thread, move_data_thread, histo_thread))
                else:
                    histo_thread.start()
            if script == "log_histograms":
                log_histo_thread = Thread(target=self.log_histograms)
                if move_data_thread.is_alive():
                    self.event = Clock.schedule_interval(partial(self.check_for_thread, move_data_thread, log_histo_thread), 1)
                elif histo_thread.is_alive():
                    self.event = Clock.schedule_interval(partial(self.check_for_thread, histo_thread, log_histo_thread), 1)
                else:
                    log_histo_thread.start()
        
    def check_for_thread(self, thread1, thread2, dt):
        if thread1.is_alive():
            pass
        elif thread2.is_alive():
            self.event.cancel()
        else:
            thread2.start()
    

    While the method works well in this instance, you would need to add a new if script == "function_tag" section to the code for each process you wanted. This could potentially lead to larger and larger sections of code for each combination of previous processes and the last process. If anyone knows of a slicker, more upscalable version, I'm all ears!