是否可以在事件之后使用tkinter类内部的current.futures执行函数/方法?如果是,怎么办? [英] Is it possible to use concurrent.futures to execute a function/method inside a tkinter class following an event? If yes, how?

查看:77
本文介绍了是否可以在事件之后使用tkinter类内部的current.futures执行函数/方法?如果是,怎么办?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用 concurrent.futures.ProcessPoolExecutor 提供的工作人员池来加快tkinter类中方法的性能。这是因为执行该方法需要占用大量CPU,并且并行化它可以缩短完成该过程的时间。我希望对照控件(以相同方法的串行执行)对它的性能进行基准测试。我已经编写了一个tkinter GUI测试代码来执行此基准测试。该方法的串行执行有效,但并发部分无效。感谢所有帮助使我的代码的并发部分正常工作的帮助。



更新:我确保已正确实现了 concurrent.futures.ProcessPoolExecutor 解决我在Tk()之外的问题,即从标准python3脚本解决。在答案中对此进行了说明。现在,我想实现该答案中描述的并发方法,以在tkinter.Tk()GUI中使用按钮。



下面给出了我的测试代码。运行它时,将显示一个GUI。当您点击查找按钮时,_findmatch函数将以串行和并发方式执行,以查找数字5在0到1E8的数字范围内出现了多少次。串行部分有效,但并行部分抱怨(请参阅下文)。 有人知道如何解决此酸洗错误吗?

 跟踪(最近一次通话):
文件 /usr/lib/python3.5/multiprocessing/queues.py,行241,在_feed中
obj = ForkingPickler.dumps(obj)
文件 / usr / lib / python3。 5 / multiprocessing / reduction.py,第50行,在转储
cls(buf,protocol).dump(obj)
_pickle.PicklingError:无法腌制<类'_tkinter.tkapp'> ;:_tkinter上的属性查找tkapp失败

测试代码:

 #!/ usr / bin / python3 
#-*-编码:utf-8-*-

将tkinter导入为tk#Python 3 tkinter模块
导入tkinter.ttk作为ttk
并发。从cf
开始导入时间,从itertools导入
休眠,链

类App(ttk.Frame):
def __init __(自己,父母):
#初始化App Frame
ttk.Frame .__ init __(self, parent,style ='App.TFrame')
self.parent =父级

self.button = ttk.Button(self,style ='start.TButton',text ='FIND',
command = self._check)
self.label0 = ttk.Label(自我,前景色='蓝色')
self.label1 = ttk.Label(自我,前景色='红色')
self.label2 = ttk.Label(自我,前景色=绿色)
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 ='单击查找以查看如何很多次出现数字5。')
self.label1.configure(text ='Serial Method:')
self.label2.configure(text ='Concurrent Method:')

def _check(self):
#初始化
self._labels()
nmax = int(1E7)
smatch = []
cmatch = []
数字='5'
self.label0.configure(
text =' {0}在0到{1}中出现的次数。'。format(
number,nmax))
self.parent.update_idletasks()

#运行序列号
start = time()
smatch = self._findmatch(0,nmax,number)
end = time()-开始
self.label1.configure(
文字='序列:发现{0}次,发现时间:{1:.6f} sec'.format(
len(smatch),end))

#同时运行串行代码并发。未来
工人= 6#工人池
chunks_vs_workers = 30#系数=> 14可以提供最佳性能
num_of_chunks = chunks_vs_workers *工人
开始=时间()
cmatch = self._concurrent_map(nmax,number,worker,num_of_chunks)
结尾= time()-开始
self.label2.configure(
text ='并发:发现{0}次事件,发现时间:{1:.6f} sec'.format(
len(cmatch),end))

def _findmatch(self,nmin,nmax,number):
'''查找范围从nmin到nmax的数字的出现并返回
列表中发现的事件。'''
start = time()
match = []
for n in range(nmin,nmax):
如果数字在str(n)中:match.append(n)
end = time()-开始
#print( \n def _findmatch {0:< 10} {1:< 10} {2:< 3}在{4:.4f} sec中找到了{3:8}。
#format(nmin,nmax,number,len(match),end))
返回匹配

def _concurrent_map(self,nmax,number,worker,num_of_chunks):
'''利用并发.futures.ProcessPoolExecutor.map到
的函数以并发
的方式在一个数字范围内查找给定数字的出现。'''
#1 。局部变量
start = time()
chunksize = nmax // num_of_chunks
#2。以cf.ProcessPoolExecutor(max_workers = workers)作为执行者的并行化

#2.1。离散化工作负载并提交给工作人员池
cstart =(chunksize * i for range in(num_of_chunks))
cstop =(chunksize * i if i!= num_of_chunks else nmax
for i in range (1,num_of_chunks + 1))
Futures = executor.map(self._findmatch,cstart,cstop,repeat(number))
end = time()-开始
print('\在def _concurrent_map语句中的\n(nmax,number,worker,num_of_chunks):')
print(在{0:.4f} sec中找到 .format(end))
返回列表(链.from_iterable(futures))


如果__name__ =='__main__':
root = tk.Tk()
root.title('App'), root.geometry('550x60')
app = App(root)
app.grid(row = 0,column = 0,sticky ='nsew')

根rowconfigure(0,权重= 1)
root.columnconfigure(0,权重= 1)
app.columnconfigure(0,权重= 1)

app.mainloop()


解决方案

我终于找到了回答我问题的方法。



Mark Summerfields的书《 Python in Practice》(2014年)提到 multiprocessing 模块,由<$ c调用$ c> concurrent.futures.ProcessPoolExecutor ,只能调用可导入的函数,并使用可腌制的模块数据(由函数调用的 )。因此,必须在与tkinter GUI模块不同的单独模块中找到 concurrent.futures.ProcessPoolExecutor 及其调用的函数(及其参数),否则它将



这样,我创建了一个单独的类来托管与 concurrent.futures.ProcessPoolExecutor 及其调用的函数和数据,而不是像以前一样将它们放入类应用程序的tkinter.Tk()GUI类中。



我还设法使用 threading.Threads 来执行我的串行和并发任务的并发执行。



我在下面分享我经过修订的测试代码,以演示我是如何做到的,希望这对尝试使用 concurrent.futures 与tkinter。



很高兴看到所有CPU都随着Tk GUI加速运行。 :)



修订后的测试代码:

 #!/ usr / bin / python3 
#-*-编码:utf-8-*-
'''代码演示如何在tkinter中使用并发.futures.Executor对象。'' '

将tkinter作为tk导入#Python 3 tkinter模块
将tkinter.ttk作为ttk
并发导入。期货作为cf
导入线程
导入时间,从itertools导入链中的睡眠
导入链


类App(ttk.Frame):
def __init __(自己,父母):
#初始化应用程序框架
ttk.Frame .__ init __(自身,父级)
self.parent =父级

self.button = ttk.Button(self,text ='FIND',command = self._check)
self.label0 = ttk.Label(自我,前景='blue')
self.label1 = ttk.Label(自我,前景='红色')
self .label2 = ttk.Label(自我,前景色='绿色')
self._labels()
self.button.grid(行= 0,列= 1,行跨度= 3,粘性='n sew')
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 ='单击查找以查看数字5出现了多少次。')
self.label1.configure(text ='Serial Method:')
self.label2.configure(text ='Concurrent方法:')

def _check(self):
#初始化
self._labels()
nmax = int(1E8)
worker = 6 #工人池
chunks_vs_workers = 30#系数=> 14可以提供最佳性能
num_of_chunks = chunks_vs_workers *工人
数字='5'
self.label0.configure (
text ='寻找{0}在0到{1}中出现的次数'.format(
number,nmax))
self.parent.update_idletasks()
#使用线程
self.serworker = threading.Thread(target = self._serial,
args =(0,nmax,number))
self.subworker =并行管理串行和并发任务threading.Thread(target = self._concurrent,
args =(nmax,number,worker,
num_of_chunks))
self.serworker.start()
self.subworker.start ()

def _serial(self,nmin,nmax,number):
fm = Findmatch
#运行序列号
start = time()
smatch = fm._findmatch(fm,0,nmax,number)
end = time()-开始
self.label1.configure(
text ='Serial Method:{0}计算时间:{1:.6f} sec'.format(
len(smatch),end))
self.parent.update_idletasks()
#print('smatch =',smatch )

def _concurrent(self,nmax,number,worker,num_of_chunks):
fm = Findmatch
#与serial.futures同时运行串行代码.submit()
开始= time()
cmatch = fm._concurrent_submit(fm,nmax,number,worker,
num_of_chunks)
end = time()-开始
self.label2.configure (
text ='并发方法:{0}次,计算时间:{1:.6f} sec'.format(
len(cmatch),end))
self.parent。 update_idletasks()
#print('cmatch =',cmatch)


class Findmatch:
'''为托管并发而专门创建的类。
,以便可以通过多处理
模块访问其调用的函数。多处理要求:代码必须是可导入的,代码
数据必须是可选取的。参考马克·萨默菲尔德斯(Mark Summerfields)撰写的《 Python in Practice》,
第4.3.2节,第173页,2014'''
def __init __(self):
self .__ init __(self)

def _findmatch(self,nmin,nmax,number):
'''用于查找nmin到nmax范围内数字的出现并返回
列表中找到的出现的函数。'''
开始= time()
match = []
对于范围n中的n(nmin,nmax):
如果str(n)中的数字为:match.append(n)
end = time()-开始
#print( \n def _findmatch {0:< 10} {1:< 10} {2:< 3}在{3:8}中找到{4:.4f} sec。
#format(nmin,nmax,number,len(match),end))
返回匹配

def _concurrent_submit(self,nmax ,number,worker,num_of_chunks):
'''利用并发.futures.ProcessPoolExecutor.submit到
的函数以并发
的方式查找在数字范围内给定数字的出现。 '' ’
#1.局部变量
start = time()
chunksize = nmax // num_of_chunks
self.futures = []
#2。以cf.ProcessPoolExecutor(max_workers = workers)作为执行者的并行化

#2.1。离散化工作负载并提交给范围为(num_of_chunks)的i的工作人员池

cstart =块大小* i
cstop =块大小*(i +1),如果i!= num_of_chunks-1 else nmax
self.futures.append(executor.submit(
self._findmatch,self,cstart,cstop,number))
end = time()-开始
print('\在def _concurrent_submit(nmax,number,worker,num_of_chunks)语句中的'n:')
print(在{0:.4f} sec中找到 .format(end))
返回列表(链.from_iterable(f.result()for cf.as_completed(
self.futures)))


如果__name__ =='__main__':
根= tk.Tk()
root.title('App'),root.geometry('550x60')
app = App(root)
app.grid(row = 0,列= 0,sticky ='nsew')

root.rowconfigure(0,weight = 1)
root.columnconfigure(0,weight = 1)
app.columnconfigure(0 ,weight = 1)

app.mainloop()


I am trying to use the pool of workers provided by concurrent.futures.ProcessPoolExecutor to speed up the performance of a method inside a tkinter class. This is because executing the method is cpu intensive and "parallelizing" it should shorten the time to complete it. I hope to benchmark it's performance against a control - a serial execution of the same method. I have written a tkinter GUI test code to perform this benchmark. The serial execution of the method works but the concurrent part does not work. Appreciate any help to get the concurrent part of my code to work.

Update: I have ensured that I have correctly implemented concurrent.futures.ProcessPoolExecutor to solve my problem outside of Tk(), i.e. from a standard python3 script. It is explained in this answer. Now I want to implement the concurrent method described in that answer to work with a button in my tkinter.Tk() GUI.

My test code is given below. When you run it, a GUI will appear. When you are click the 'FIND' Button, the _findmatch function will be executed in a serial and concurrent manner to find how many times the number 5 occurs in the number range of 0 to 1E8. The serial part works but the concurrent part is complaining (see below). Anyone knows how to fix this Pickling error?

Traceback (most recent call last):
  File "/usr/lib/python3.5/multiprocessing/queues.py", line 241, in _feed
    obj = ForkingPickler.dumps(obj)
  File "/usr/lib/python3.5/multiprocessing/reduction.py", line 50, in dumps
    cls(buf, protocol).dump(obj)
_pickle.PicklingError: Can't pickle <class '_tkinter.tkapp'>: attribute lookup tkapp on _tkinter failed

Test Code:

#!/usr/bin/python3
# -*- coding: utf-8 -*-

import tkinter as tk # Python 3 tkinter modules
import tkinter.ttk as ttk
import concurrent.futures as cf
from time import time, sleep
from itertools import repeat, chain 

class App(ttk.Frame):
    def __init__(self, parent):
        # Initialise App Frame
        ttk.Frame.__init__(self, parent, style='App.TFrame')
        self.parent=parent

        self.button = ttk.Button(self, style='start.TButton', 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(1E7)
        smatch=[]
        cmatch=[]
        number = '5'
        self.label0.configure(
            text='Finding the number of times {0} appears in 0 to {1}'.format(
                number, nmax))
        self.parent.update_idletasks()

        # Run serial code
        start = time()
        smatch = self._findmatch(0, nmax, number)
        end = time() - start
        self.label1.configure(
            text='Serial: Found {0} occurances,  Time to Find: {1:.6f}sec'.format(
                len(smatch), end))

        # Run serial code concurrently with concurrent.futures
        workers = 6     # Pool of workers
        chunks_vs_workers = 30 # A factor of =>14 can provide optimum performance 
        num_of_chunks = chunks_vs_workers * workers
        start = time()
        cmatch = self._concurrent_map(nmax, number, workers, num_of_chunks)
        end = time() - start
        self.label2.configure(
            text='Concurrent: Found {0} occurances,  Time to Find: {1:.6f}sec'.format(
                len(cmatch), end))

    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_map(self, nmax, number, workers, num_of_chunks):
        '''Function that utilises concurrent.futures.ProcessPoolExecutor.map 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
        #2. Parallelization
        with cf.ProcessPoolExecutor(max_workers=workers) as executor:
            # 2.1. Discretise workload and submit to worker pool
            cstart = (chunksize * i for i in range(num_of_chunks))
            cstop = (chunksize * i if i != num_of_chunks else nmax
                     for i in range(1, num_of_chunks + 1))
            futures = executor.map(self._findmatch, cstart, cstop, repeat(number))
        end = time() - start
        print('\n within statement of def _concurrent_map(nmax, number, workers, num_of_chunks):')
        print("found in {0:.4f}sec".format(end))
        return list(chain.from_iterable(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()

解决方案

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 modules
import tkinter.ttk as ttk
import concurrent.futures as cf
import threading
from time import time, sleep
from 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()

这篇关于是否可以在事件之后使用tkinter类内部的current.futures执行函数/方法?如果是,怎么办?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆