Is it possible to use concurrent.futures to execute a function/method inside a tkinter class following an event? If yes, how? Is it possible to use concurrent.futures to execute a function/method inside a tkinter class following an event? If yes, how? tkinter tkinter

Is it possible to use concurrent.futures to execute a function/method inside a tkinter class following an event? If yes, how?


I finally found a way to answer my question.

Mark Summerfields's book, Python in Practice(2014), mentioned that the multiprocessing module, called by concurrent.futures.ProcessPoolExecutor, can only call functions that are importable and use modules data (called by the functions) that are pickleable. As such, it is necessary for concurrent.futures.ProcessPoolExecutor and the functions (with its argument) it called to be found in a separate module than the tkinter GUI module, else it would not work.

As such, I created a separate class to host all the codes related to concurrent.futures.ProcessPoolExecutor and the functions and data it called, instead of putting them in the class app, my tkinter.Tk() GUI class, as I did previously. It worked!

I also managed to use threading.Threads to perform concurrent execution of my serial and concurrent tasks.

I am sharing my revised test code below to demonstrate how I did it and hope this helps anyone attempting to use concurrent.futures with tkinter.

It's really beautiful to see all the CPUs revving up with Tk GUI. :)

Revised Test Code:

#!/usr/bin/python3# -*- coding: utf-8 -*-''' Code to demonstrate how to use concurrent.futures.Executor object with tkinter.'''import tkinter as tk # Python 3 tkinter modulesimport tkinter.ttk as ttkimport concurrent.futures as cfimport threadingfrom time import time, sleepfrom itertools import chain class App(ttk.Frame):    def __init__(self, parent):        # Initialise App Frame        ttk.Frame.__init__(self, parent)        self.parent=parent        self.button = ttk.Button(self, text = 'FIND', command=self._check)        self.label0 = ttk.Label(self, foreground='blue')        self.label1 = ttk.Label(self, foreground='red')        self.label2 = ttk.Label(self, foreground='green')        self._labels()        self.button.grid(row=0, column=1, rowspan=3, sticky='nsew')        self.label0.grid(row=0, column=0, sticky='nsew')        self.label1.grid(row=1, column=0, sticky='nsew')        self.label2.grid(row=2, column=0, sticky='nsew')    def _labels(self):        self.label0.configure(text='Click "FIND" to see how many times the number 5 appears.')        self.label1.configure(text='Serial Method:')        self.label2.configure(text='Concurrent Method:')    def _check(self):        # Initialisation        self._labels()        nmax = int(1E8)        workers = 6     # Pool of workers        chunks_vs_workers = 30 # A factor of =>14 can provide optimum performance         num_of_chunks = chunks_vs_workers * workers        number = '5'        self.label0.configure(            text='Finding the number of times {0} appears in 0 to {1}'.format(                number, nmax))        self.parent.update_idletasks()        # Concurrent management of serial and concurrent tasks using threading        self.serworker = threading.Thread(target=self._serial,                                          args=(0, nmax, number))        self.subworker  = threading.Thread(target=self._concurrent,                                           args=(nmax, number, workers,                                                 num_of_chunks))        self.serworker.start()                 self.subworker.start()             def _serial(self, nmin, nmax, number):        fm = Findmatch        # Run serial code        start = time()        smatch = fm._findmatch(fm, 0, nmax, number)        end = time() - start        self.label1.configure(            text='Serial Method: {0} occurrences, Compute Time: {1:.6f}sec'.format(                len(smatch), end))        self.parent.update_idletasks()        #print('smatch = ', smatch)     def _concurrent(self, nmax, number, workers, num_of_chunks):         fm = Findmatch        # Run serial code concurrently with concurrent.futures .submit()        start = time()        cmatch = fm._concurrent_submit(fm, nmax, number, workers,                                        num_of_chunks)        end = time() - start        self.label2.configure(            text='Concurrent Method: {0} occurrences, Compute Time: {1:.6f}sec'.format(                len(cmatch), end))        self.parent.update_idletasks()        #print('cmatch = ', cmatch) class Findmatch:    ''' A class specially created to host concurrent.futures.ProcessPoolExecutor        so that the function(s) it calls can be accessible by multiprocessing        module. Multiprocessing requirements: codes must be importable and code        data must be pickerable. ref. Python in Practice, by Mark Summerfields,        section 4.3.2, pg 173, 2014'''    def __init__(self):        self.__init__(self)    def _findmatch(self, nmin, nmax, number):        '''Function to find the occurence of number in range nmin to nmax and return           the found occurences in a list.'''        start = time()        match=[]        for n in range(nmin, nmax):            if number in str(n): match.append(n)        end = time() - start        #print("\n def _findmatch {0:<10} {1:<10} {2:<3} found {3:8} in {4:.4f}sec".        #      format(nmin, nmax, number, len(match),end))        return match    def _concurrent_submit(self, nmax, number, workers, num_of_chunks):        '''Function that utilises concurrent.futures.ProcessPoolExecutor.submit to           find the occurrences of a given number in a number range in a concurrent           manner.'''        # 1. Local variables        start = time()        chunksize = nmax // num_of_chunks        self.futures = []        #2. Parallelization        with cf.ProcessPoolExecutor(max_workers=workers) as executor:            # 2.1. Discretise workload and submit to worker pool            for i in range(num_of_chunks):                cstart = chunksize * i                cstop = chunksize * (i + 1) if i != num_of_chunks - 1 else nmax                self.futures.append(executor.submit(                    self._findmatch, self, cstart, cstop, number))        end = time() - start        print('\n within statement of def _concurrent_submit(nmax, number, workers, num_of_chunks):')        print("found in {0:.4f}sec".format(end))        return list(chain.from_iterable(f.result() for f in cf.as_completed(            self.futures)))if __name__ == '__main__':    root = tk.Tk()    root.title('App'), root.geometry('550x60')    app = App(root)    app.grid(row=0, column=0, sticky='nsew')    root.rowconfigure(0, weight=1)    root.columnconfigure(0, weight=1)    app.columnconfigure(0, weight=1)    app.mainloop()