我试图理解多线程,并试图执行以下代码,但得到错误。请帮忙解决这个问题。
from tkinter import *
from tkinter.ttk import *
import tkinter as tk
import datetime
import multiprocessing
process1 = None
class App:
def __init__(self):
self.root = Tk()
self.top_frame = tk.Frame(self.root, height=50, pady=3)
self.selectFile = tk.Button(self.top_frame, text="Start", activebackground="blue",
command=lambda: self.create_process()).pack()
self.progressbar_frame = tk.Frame(self.root)
self.pgbar = Progressbar(self.progressbar_frame, length=125, orient=HORIZONTAL, mode="indeterminate")
self.pgbar.pack()
self.top_frame.pack()
self.root.mainloop()
def calculate_data(self):
a = datetime.datetime.now()
i = 0
while i < 100000000:
i+=1
print(i)
b = datetime.datetime.now()
print(b - a)
def create_process(self):
#self.pgbar_start()
global process1
process1 = multiprocessing.Process(target=self.calculate_data, args=())
process2 = multiprocessing.Process(target=self.pgbar_start, args=())
process1.start()
process2.start()
self.periodic_call()
def pgbar_start(self):
self.progressbar_frame.pack()
self.pgbar.start(10)
def pgbar_stop(self):
self.pgbar.stop()
self.progressbar_frame.pack_forget()
def periodic_call(self):
if process1.is_alive():
self.pgbar.after(1000, self.periodic_call)
else:
self.pgbar_stop()
if __name__ == "__main__":
app = App()
我得到以下错误:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:Program FilesPython37libtkinter__init__.py", line 1705, in __call__
return self.func(*args)
File "C:/Python Automation/Practice/multi_processing.py", line 15, in <lambda>
command=lambda: self.create_process()).pack()
File "C:/Python Automation/Practice/multi_processing.py", line 37, in create_process
process1.start()
File "C:Program FilesPython37libmultiprocessingprocess.py", line 112, in start
self._popen = self._Popen(self)
File "C:Program FilesPython37libmultiprocessingcontext.py", line 223, in _Popen
return _default_context.get_context().Process._Popen(process_obj)
File "C:Program FilesPython37libmultiprocessingcontext.py", line 322, in _Popen
return Popen(process_obj)
File "C:Program FilesPython37libmultiprocessingpopen_spawn_win32.py", line 89, in __init__
reduction.dump(process_obj, to_child)
File "C:Program FilesPython37libmultiprocessingreduction.py", line 60, in dump
ForkingPickler(file, protocol).dump(obj)
TypeError: can't pickle _tkinter.tkapp objects
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "C:Program FilesPython37libmultiprocessingspawn.py", line 105, in spawn_main
exitcode = _main(fd)
File "C:Program FilesPython37libmultiprocessingspawn.py", line 115, in _main
self = reduction.pickle.load(from_parent)
EOFError: Ran out of input
请帮助我了解我做错了什么。我的目标是运行进度条在tkinter窗口与后台进程。进度条应该运行流畅。
也许我误解了某事,但我很确定你是在询问多处理,或者至少是在你的代码中,所以这里是如何与tkinter(代码注释中的解释)相结合(并通过"in combination"我的意思是tkinter
总是在主进程中在一个线程中所以其他的东西,比如"计算"在这种情况下,将被移动到其他线程或进程):
# import what is needed and don't ask about threading yet that will be explained
# (pretty useless comment anyways)
from tkinter import Tk, Button, Label, DoubleVar
from tkinter.ttk import Progressbar
from multiprocessing import Process, Queue
from threading import Thread
from queue import Empty
from time import perf_counter, sleep
# create main window also inherit from `Tk` to make the whole thing a bit easier
# because it means that `self` is the actual `Tk` instance
class MainWindow(Tk):
def __init__(self):
Tk.__init__(self)
# prepare the window, some labels are initiated but not put on the screen
self.geometry('400x200')
self.btn = Button(
self, text='Calculate', command=self.start_calc
)
self.btn.pack()
self.info_label = Label(self, text='Calculating...')
# progressbar stuff
self.progress_var = DoubleVar(master=self, value=0.0)
self.progressbar = Progressbar(
self, orient='horizontal', length=300, variable=self.progress_var
)
# create a queue for communication
self.queue = Queue()
# the method to launch the whole process and start the progressbar
def start_calc(self):
self.info_label.pack()
self.progressbar.pack()
Process(target=calculation, args=(self.queue, ), daemon=True).start()
self.update_progress()
# this function simply updates the `DoubleVar` instance
# that is assigned to the Progressbar so basically makes
# the progressbar move
def update_progress(self):
try:
data = self.queue.get(block=False)
except Empty:
pass
else:
# if the process has finished stop this whole thing (using `return`)
if data == 'done':
self.info_label.config(text='Done')
self.progress_var.set(100)
return
self.progress_var.set(data)
finally:
self.after(100, self.update_progress)
# interestingly this function couldn't be a method of the class
# because the class was inheriting from `Tk` (at least I think that is the reason)
# and as mentioned `tkinter` and multiprocessing doesn't go well together
def calculation(queue):
# here is the threading this is important because the below
# "calculation" is super quick while the above update loop runs only every
# 100 ms which means that the Queue will be full and this process finished
# before the progressbar will show that it is finished
# so this function in a thread will only put stuff in the queue
# every 300 ms giving time for the progressbar to update
# if the calculation takes longer per iteration this part is not necessary
def update_queue():
while True:
sleep(0.3)
queue.put(i / range_ * 100) # put in percentage as floating point where 100 is 100%
# here starting the above function again if the calculations per iteration
# take more time then it is fine to not use this
Thread(target=update_queue).start()
# starts the "calculation"
start = perf_counter()
range_ = 100_000_000
for i in range(range_):
pass
finish = perf_counter()
# put in the "sentinel" value to stop the update
# and notify that the calculation has finished
queue.put('done')
# could actually put the below value in the queue to and
# handle so that this is show on the `tkinter` window
print((finish - start))
# very crucial when using multiprocessing always use the `if __name__ == "__main__":` to avoid
# recursion or sth because the new processes rerun this whole thing so it can end pretty badly
# there is sth like a fail safe but remember to use this anyways (a good practice in general)
if __name__ == '__main__':
# as you can see then inheriting from `Tk` means that this can be done too
root = MainWindow()
root.mainloop()
<<p>非常重要/strong>(建议,但你真的需要遵循,特别是在这种情况下,我已经看到至少有两个人在从tkinter
和tkinter.ttk
导入一切时已经犯了这个错误):我强烈建议不要在导入某些东西时使用通配符(
*
),你应该要么导入你需要的,例如from module import Class1, func_1, var_2
等或导入整个模块:import module
然后你也可以使用别名:import module as md
或类似的东西,关键是不要导入所有东西,除非你真正知道你在做什么;名称冲突是问题所在。
编辑:小事情真的但daemon=True
Process
实例,这样过程主要进程退出时关闭(据我所知没有退出过程的最佳方式(线程顺便说一句相同),但不知道如果这是真的那么糟糕(我猜还取决于什么过程但例如它可能不能正常关闭文件或某事喜欢,但是如果你不写文件或任何在最坏的情况下可能会像运行过程一样简单再次获得一些丢失的进度(在当前示例中,进程只能在关闭窗口或使用任务管理器或sth关闭整个程序时突然退出)))
EDIT2(相同的代码只是删除了所有的注释,使它不那么笨拙):
from tkinter import Tk, Button, Label, DoubleVar
from tkinter.ttk import Progressbar
from multiprocessing import Process, Queue
from threading import Thread
from queue import Empty
from time import perf_counter, sleep
class MainWindow(Tk):
def __init__(self):
Tk.__init__(self)
self.geometry('400x200')
self.btn = Button(
self, text='Calculate', command=self.start_calc
)
self.btn.pack()
self.info_label = Label(self, text='Calculating...')
self.progress_var = DoubleVar(master=self, value=0.0)
self.progressbar = Progressbar(
self, orient='horizontal', length=300, variable=self.progress_var
)
self.queue = Queue()
def start_calc(self):
self.info_label.pack()
self.progressbar.pack()
Process(target=calculation, args=(self.queue, ), daemon=True).start()
self.update_progress()
def update_progress(self):
try:
data = self.queue.get(block=False)
except Empty:
pass
else:
if data == 'done':
self.info_label.config(text='Done')
self.progress_var.set(100)
return
self.progress_var.set(data)
finally:
self.after(100, self.update_progress)
def calculation(queue):
def update_queue():
while True:
sleep(0.3)
queue.put(i / range_ * 100)
Thread(target=update_queue).start()
start = perf_counter()
range_ = 100_000_000
for i in range(range_):
pass
finish = perf_counter()
queue.put('done')
print((finish - start))
if __name__ == '__main__':
root = MainWindow()
root.mainloop()
如果你有任何其他问题,问他们!