我有一个Python 3。x报表创建者,其I/O绑定(由于SQL而非python),在创建报表时主窗口将“锁定”数分钟。
所需要的只是在锁定GUI时使用标准窗口操作(移动、调整大小/最小化、关闭等)(GUI上的所有其他内容都可以保持“冻结”,直到所有报告完成)。
添加20181129:换句话说,tkinter必须只控制应用程序窗口的内容,并将所有标准(外部)窗口控件的处理留给O/S。如果我能做到这一点,我的问题就消失了,我不需要使用线程/子进程(Freezeups成为可接受的行为,类似于禁用“做报告”按钮)。
做这件事的最简单/最简单的方法(=对现有代码的干扰最小)是什么?理想的方法是使用Python
下面的所有内容都是支持信息,这些信息更详细地解释了问题、尝试过的方法以及遇到的一些微妙问题。
需要考虑的事情:
>
用户选择他们的报告,然后在主窗口上按下“创建报告”按钮(当真正的工作开始和冻结发生时)。完成所有报表后,报表创建代码将显示一个(Toplevel)完成窗口。关闭此窗口可启用主窗口中的所有内容,允许用户退出程序或创建更多报告。
新增20181129:在明显的随机间隔(相隔几秒钟)我可以移动窗口。
除了显示“完成”窗口外,报告创建代码不以任何方式涉及GUI或tkinter。
报告创建代码生成的某些数据必须显示在“完成”窗口中。
没有理由“并行化”报表创建,尤其是因为同一台SQL server
如果它影响解决方案:在创建每个报告时,我最终需要在GUI上显示报告名称(现在显示在控制台上)。
第一次用python做线程/子处理,但熟悉其他语言。
新增20181129:开发环境为64位Python 3.6。使用EclipseOxygen(pydev插件)在Win10上运行4次。应用程序必须至少可移植到linux。
最简单的答案似乎是使用线程。只需要一个额外的线程(创建报告的线程)。受影响的线路:
DoChosenReports() # creates all reports (and the "Done" window)
更改为:
from threading import Thread
CreateReportsThread = Thread( target = DoChosenReports )
CreateReportsThread.start()
CreateReportsThread.join() # 20181130: line omitted in original post, comment out to unfreeze GUI
成功生成报告,其名称在创建时显示在控制台上
但是,GUI仍然处于冻结状态,“完成”窗口(现在由新线程调用)从未出现。这会让用户无所适从,无法做任何事情,并且不知道发生了什么(如果有的话)(这就是为什么我希望在创建文件名时在GUI上显示文件名的原因)。
顺便说一句,报告完成后,报告创建线程必须在显示完成窗口之前(或之后)悄悄自杀。
我也试过使用
from multiprocessing import Process
ReportCreationProcess = Process( target = DoChosenReports )
ReportCreationProcess.start()
但这与主要项目“如果(_name_ == '_主要_):' " 测试”相冲突。
添加了20181129:刚刚发现了wait_变量()
universalwidget方法)。基本思想是将createreport代码作为一个doforever线程(守护进程?)由此方法控制(执行由GUI中的Do reports按钮控制)。
从web研究中,我知道所有tkinter操作都应该在主(父)线程中执行,这意味着我必须将“完成”窗口移动到该线程
我还需要该窗口来显示它从“子”线程接收的一些数据(三个字符串)。我正在考虑使用use应用程序级globals作为信号量(仅由createreport线程写入,仅由主程序读取)来传递数据。我知道,如果有两个以上的线程,但执行更多的操作(例如,使用队列?)对我来说,简单的情况似乎有点过分了。
总而言之:当窗口因任何原因被冻结时,允许用户操作(移动、调整大小、最小化等)应用程序主窗口的最简单方法是什么。换句话说,O/S而不是tkinter必须控制主窗口的框架(外部)
答案需要在python 3.2上使用。2以跨平台方式(至少在窗户上
我在我的一本书中找到了一个很好的例子,类似于你想做的,我认为它展示了一种使用tkinter线程的好方法。这是Alex Martinelli和David Ascher在《Python食谱》第一版中介绍的将Tkinter和异步I/O与线程相结合的方法9.6。代码是为Python2编写的。x、 但在Python3中只需稍作修改即可工作。
正如我在一篇评论中所说,如果您想与GUI eventloop进行交互,或者只是想调整窗口大小或移动窗口,则需要保持GUI eventloop运行。下面的示例代码通过使用队列
将数据从后台处理线程传递到主GUI线程来实现这一点。
tkinter在()之后有一个名为的通用函数,它可以用来安排一个函数在经过一定时间后被调用。在下面的代码中,有一个名为
之后调用periodic_call()
的方法,它处理队列中的任何数据,然后在(),以便在短时间延迟后安排另一个调用,以便队列数据处理继续。
由于()
之后的是tkinter的一部分,它允许
main循环()
继续运行,这使得GUI在这些周期性队列检查之间保持“活性”。如果需要,它还可以调用tkinter
来更新GUI(不像在单独的线程中运行的代码)。
from itertools import count
import sys
import tkinter as tk
import tkinter.messagebox as tkMessageBox
import threading
import time
from random import randint
import queue
# Based on example Dialog
# http://effbot.org/tkinterbook/tkinter-dialog-windows.htm
class InfoMessage(tk.Toplevel):
def __init__(self, parent, info, title=None, modal=True):
tk.Toplevel.__init__(self, parent)
self.transient(parent)
if title:
self.title(title)
self.parent = parent
body = tk.Frame(self)
self.initial_focus = self.body(body, info)
body.pack(padx=5, pady=5)
self.buttonbox()
if modal:
self.grab_set()
if not self.initial_focus:
self.initial_focus = self
self.protocol("WM_DELETE_WINDOW", self.cancel)
self.geometry("+%d+%d" % (parent.winfo_rootx()+50, parent.winfo_rooty()+50))
self.initial_focus.focus_set()
if modal:
self.wait_window(self) # Wait until this window is destroyed.
def body(self, parent, info):
label = tk.Label(parent, text=info)
label.pack()
return label # Initial focus.
def buttonbox(self):
box = tk.Frame(self)
w = tk.Button(box, text="OK", width=10, command=self.ok, default=tk.ACTIVE)
w.pack(side=tk.LEFT, padx=5, pady=5)
self.bind("<Return>", self.ok)
box.pack()
def ok(self, event=None):
self.withdraw()
self.update_idletasks()
self.cancel()
def cancel(self, event=None):
# Put focus back to the parent window.
self.parent.focus_set()
self.destroy()
class GuiPart:
TIME_INTERVAL = 0.1
def __init__(self, master, queue, end_command):
self.queue = queue
self.master = master
console = tk.Button(master, text='Done', command=end_command)
console.pack(expand=True)
self.update_gui() # Start periodic GUI updating.
def update_gui(self):
try:
self.master.update_idletasks()
threading.Timer(self.TIME_INTERVAL, self.update_gui).start()
except RuntimeError: # mainloop no longer running.
pass
def process_incoming(self):
""" Handle all messages currently in the queue. """
while self.queue.qsize():
try:
info = self.queue.get_nowait()
InfoMessage(self.master, info, "Status", modal=False)
except queue.Empty: # Shouldn't happen.
pass
class ThreadedClient:
""" Launch the main part of the GUI and the worker thread. periodic_call()
and end_application() could reside in the GUI part, but putting them
here means all the thread controls are in a single place.
"""
def __init__(self, master):
self.master = master
self.count = count(start=1)
self.queue = queue.Queue()
# Set up the GUI part.
self.gui = GuiPart(master, self.queue, self.end_application)
# Set up the background processing thread.
self.running = True
self.thread = threading.Thread(target=self.workerthread)
self.thread.start()
# Start periodic checking of the queue.
self.periodic_call(200) # Every 200 ms.
def periodic_call(self, delay):
""" Every delay ms process everything new in the queue. """
self.gui.process_incoming()
if not self.running:
sys.exit(1)
self.master.after(delay, self.periodic_call, delay)
# Runs in separate thread - NO tkinter calls allowed.
def workerthread(self):
while self.running:
time.sleep(randint(1, 10)) # Time-consuming processing.
count = next(self.count)
info = 'Report #{} created'.format(count)
self.queue.put(info)
def end_application(self):
self.running = False # Stop queue checking.
self.master.quit()
if __name__ == '__main__': # Needed to support multiprocessing.
root = tk.Tk()
root.title('Report Generator')
root.minsize(300, 100)
client = ThreadedClient(root)
root.mainloop() # Display application window and start tkinter event loop.
我修改了这个问题,把意外遗漏但关键的一行也包括进去。避免GUI冻结的答案非常简单:
Don't call ".join()" after launching the thread.
除上述之外,完整的解决方案包括:
使用multiprocessing.dummy模块的一个简单方法是:
from multiprocessing.dummy import Process
ReportCreationProcess = Process( target = DoChosenReports )
ReportCreationProcess.start()
同样,请注意缺少一个。join()行。
作为一种临时攻击,“完成”窗口仍由createreport线程在退出之前创建。这可以工作,但确实会导致此运行时错误:
RuntimeError: Calling Tcl from different appartment
然而,这个错误似乎不会引起问题。而且,正如其他问题所指出的,可以通过将“完成”窗口的创建移动到主线程中(并让创建报告线程发送事件“启动”该窗口)来消除错误。
最后,我感谢@泰格霍克T3(他对我正在采取的方法进行了很好的概述)和@马蒂诺,他们介绍了如何处理更一般的情况,并引用了看起来很有用的资源。这两个答案都值得一读。
您需要两个函数:第一个封装程序的长期运行工作,第二个创建一个处理第一个函数的线程。如果您需要线程立即停止,如果用户在线程仍在运行时关闭程序(不推荐),请使用守护程序
标志或查看事件
对象。如果您不希望用户能够在完成之前再次调用该函数,请在启动时禁用按钮,然后在结束时将按钮设置为正常。
import threading
import tkinter as tk
import time
class App:
def __init__(self, parent):
self.button = tk.Button(parent, text='init', command=self.begin)
self.button.pack()
def func(self):
'''long-running work'''
self.button.config(text='func')
time.sleep(1)
self.button.config(text='continue')
time.sleep(1)
self.button.config(text='done')
self.button.config(state=tk.NORMAL)
def begin(self):
'''start a thread and connect it to func'''
self.button.config(state=tk.DISABLED)
threading.Thread(target=self.func, daemon=True).start()
if __name__ == '__main__':
root = tk.Tk()
app = App(root)
root.mainloop()
我在JFace/SWT的帮助下用Java构建了一个应用程序。我主要使用JFace的,有时使用后面的SWT表。 我的表有一个标题(用列名填充),第一行用(过滤器的下拉菜单)中的呈现。 现在我要修复表中的第一行(“filter-row”),所以无论我是否向下滚动,它总是独立显示。 你知道有什么机会可以这样做吗(而不是像我在网上发现的那样,把一个表拆分成两个表)?
我正在上谷歌Android应用程序课程,我遇到了一个奇怪的错误。如果调用,则不会出现布局,如果注释掉,则会出现布局。如果我在调用后返回,我将看到布局,如果我在调用后返回,则没有布局。 布局
我正在使用intellij并遵循此文档: https://www.playframework.com/documentation/2.5.x/Migration25 我更改了插件。sbt如下: 然后它就卡住了: 我检查了这个存储库,没有2.5.3版本。 我做错了什么? 这是我的身材。sbt: 名称:="播放" 版本:="1.0" lazy val播放(文件中的项目(“.”)。enablePlugi
可能重复: Java等待并通知:IllegalMonitorStateException 有什么问题 投掷:
当数据被不可变地借用时,它还会冻结(freeze)。已冻结(frozen)数据无法通过原始对象来修改,直到指向这些数据的所有引用离开作用域为止。 fn main() { let mut _mutable_integer = 7i32; { // 借用 `_mutable_integer` let _large_integer = &_mutable_
我有一个本体文件,正在使用OWL-API。我应该为我的类(#Doc)检索她的个人和他们的对象属性 实际上我尝试了两种方法来获取个人,但我总是遇到以下错误: 线程“main”java中出现异常。lang.NoClassDefFoundError:javax/inject/Provider (我想这意味着编译器找不到我的类!)