0

One of buttons in my qt gui starts ProcessPoolExecutor which is making tifs images. I want add process bar but my gui is freezing on time when the event loop is working.

    @pyqtSlot()
    def on_pb_start_clicked(self):
        if self.anaglyph_output != None and self.tmp_output != None:
            progress_value = 1
            self.progress_display(progress_value)
            df = self.mask_df
            df_size = 10
           
            workers = self.cpu_selected
            args = [(i, df[df.fid == i + 1.0], self.anaglyph_output, self.tmp_output) for i in range(df_size)]
            tasks = []

            with ProcessPoolExecutor(max_workers=workers) as executor:
                for arg in args:
                    tasks.append(asyncio.get_event_loop().run_in_executor(executor, create_anaglyph, *arg))

            loop = asyncio.get_event_loop().run_until_complete(asyncio.gather(*tasks))

            print(loop)

        else:
            self.display_no_path_warning()

i dont know what to do

Mily
  • 3
  • 1
  • Sorry but your code is insufficient to understand the problem. Please provide a valid [mre]. – musicamante Aug 23 '23 at 11:30
  • Also, can you explain why you're using async? You only mentioned "making tifs images", but that is ambiguous: if what you actually do is to process data to create images (so, it depends on pure CPU computation), then neither threading or async will help you there, and you may need to consider multiprocessing instead. – musicamante Aug 23 '23 at 11:48
  • this is not a complete reproducible example - something the author can improve for their following questions, but, yes, it is answearable - the incorrect part that causes the blocking has enough context in here. – jsbueno Aug 24 '23 at 20:00

1 Answers1

0

In this code you are gaining nothing by using async code - and, by using it incorrectly, you are actually blocking the execution that would be parallel in the sub-processes, until all processing is finished.

asyncio in Python allow for concurrent tasks, but then everything ou are running has to be written to be colaborative parallel using the loop. What you are doing is inside a single-threaded callback from Qt, your on_pb_start_clicked, you are starting an asyncio loop, and telling it to wait until all tasks placed in it are ready. This function won't return, and therefore, the Qt UI will block.

Since you are already running a Qt application, you are better of using the Qt mechanisms for concurrency - besides the tasks that offloaded to other processes using a ProcessPoolExecutor.

In this case, since you do not seen to check the return value, you can simply submit all your tasks to the process pool, and return from your function.

If you need the return values, you have to register a callback function with Qt so that it can take the tasks list you create in this function, and call done in the futures inside them, to learn which are ready.


from PyQt5.QtCore import QTimer  # not sure if you are on PyQT5 - just find out how to import QTimer in your bindings

...

    @pyqtSlot()
    def on_pb_start_clicked(self):
        if self.anaglyph_output != None and self.tmp_output != None:
            progress_value = 1
            self.progress_display(progress_value)
            df = self.mask_df
            df_size = 10
           
            workers = self.cpu_selected
            args = [(i, df[df.fid == i + 1.0], self.anaglyph_output, self.tmp_output) for i in range(df_size)]
            # for correctness: check if "self.executor" already exists
            # and is running
            if not hasattr(self, "executor"):
                self.executor = ProcessPoolExecutor(max_workers=workers)
                self.tasks = set()
            for arg in args:
                self.tasks.add(executor.submit(create_anaglyph, *arg))
            self.executor = executor
            QTimer.singleShot(200, self.check_tasks) # callback checking in 200 miliseconds


        else:
            self.display_no_path_warning()
            
    def check_tasks(self):
        for task in self.tasks.copy(): # iterate in a copy of self.tasks to avoid side-effects of modifications on the set while iterating over it
            if task.done():
                result = task.result()  # this is the return value of the off-process execution
                self.tasks.remove(task)
                ... # do things with result
        if not self.tasks:
            self.executor.shutdown()
            del self.executor
        else:
            # if there are tasks in execution, re-schedule the check
            QTimer.singleShot(200, self.check_tasks)

jsbueno
  • 99,910
  • 10
  • 151
  • 209