Python&;Tkinter->;关于调用冻结程序的长时间运行函数
我是GUI编程方面的新手,我正在尝试为我的python解析器之一制作GUI 我知道:Python&;Tkinter->;关于调用冻结程序的长时间运行函数,python,multithreading,parsing,tkinter,Python,Multithreading,Parsing,Tkinter,我是GUI编程方面的新手,我正在尝试为我的python解析器之一制作GUI 我知道: Tkinter是单螺纹的。通过事件循环的每次行程都会发生屏幕更新。任何时候,只要有一个长时间运行的命令,就会阻止事件循环完成迭代,从而阻止事件的处理,从而防止重画 我的程序调用一个大函数,整个运行大约需要5分钟。因此,我想唯一的解决方案是对长时间运行的命令使用线程。 但是,我在中长时间运行的命令已经线程化,所以我真的不知道如何继续 -->只要我在GUI中单击BUT1,程序就会冻结,直到功能完全完成。我想在后
- Tkinter是单螺纹的。通过事件循环的每次行程都会发生屏幕更新。任何时候,只要有一个长时间运行的命令,就会阻止事件循环完成迭代,从而阻止事件的处理,从而防止重画
- 我的程序调用一个大函数,整个运行大约需要5分钟。因此,我想唯一的解决方案是对长时间运行的命令使用线程。 但是,我在中长时间运行的命令已经线程化,所以我真的不知道如何继续
- Main.py->GUI
- Module_1.py->单击按钮BUT1调用的函数
#!/usr/bin/python
# -*- coding: utf-8 -*-
from Tkinter import *
import sys
import tkMessageBox
import tkFileDialog
import Module_1
import csv
from time import strftime, gmtime
DATE = strftime("_%d_%b_%Y")
class App:
def __init__(self, master):
self.frame = Frame(master, borderwidth=5, relief=RIDGE)
self.frame.grid()
class IORedirector(object):
def __init__(self,TEXT_INFO):
self.TEXT_INFO = TEXT_INFO
class StdoutRedirector(IORedirector):
def write(self,str):
self.TEXT_INFO.config(text=self.TEXT_INFO.cget('text') + str)
self.TEXT_HEADER = self.text_intro = Label(self.frame, bg="lightblue",text="THIS IS \n MY SUPER PROGRAM")
self.TEXT_HEADER.grid(row=0, column=0, columnspan=2, sticky=W+E+N+S)
self.MENU = Frame(self.frame, borderwidth=5, relief=RIDGE, height=12)
self.MENU.grid(row=1, column=0, sticky=N)
self.button = Button(self.MENU, text="QUIT", bg="red", command=self.frame.quit)
self.button.grid(row=4, column=0)
self.BUT1 = Button(self.MENU, text="BUT1", command=self.BUT1)
self.BUT1.grid(row=0, column=0,sticky=W+E)
self.TEXT_INFO = Label(self.frame, height=12, width=40, text="SOME TEXT", bg="grey",borderwidth=5, relief=RIDGE)
self.TEXT_INFO.grid(row=1, column=1, sticky = N+W)
sys.stdout = StdoutRedirector(self.TEXT_INFO)
def BUT1(self):
self.BUT1.config(text="RUNNING")
self.TEXT_INFO.config(text="BUT1 LAUNCHED")
Module_1.main("BUT1")
## HERE WE NEED TO RUN THE FUNCTION
## THE PROGRAMM FREEZE HERE UNTIL THE FUNCTION IS ENTIRELY RUN
self.TEXT_INFO.config(text="BUT1 FINISHED")
self.BUT1.config(text="DONE")
root = Tk()
app = App(root)
root.mainloop()
下面是模块_1.py-->包含大函数
#!/usr/bin/python
# -*- coding: utf-8 -*-
import Queue
import threading
import urllib2
import time
from bs4 import BeautifulSoup as soup
from urllib2 import urlopen
import re
import os
import random
import sys
import logging
import csv
from time import strftime, gmtime
import os
import random
import shutil
import sys
import re
import logging
from threading import RLock
from time import strftime, gmtime
import csv
import urllib
from urllib import urlretrieve
from grab.spider import Spider, Task
logging.basicConfig(level=logging.CRITICAL) # Loggin to DEBUG / INFO
log = logging.getLogger()
DATE = strftime("_%d_%b_%Y")
class SPIDER1(Spider):
initial_urls = ['URL_THAT_I_NEED_TO_PARSE']
def __init__(self):
super(SPIDER1, self).__init__(
thread_number=20,
network_try_limit=20,
task_try_limit=20
)
self.result = {}
def task_initial(self, grab, task):
for opt in grab.css_list("select[name='Template$TestCentreSearch1$SubRegionList'] option")[1:]:
grab.set_input('Template$TestCentreSearch1$SubRegionList', opt.attrib['value'])
grab.submit(extra_post={
'__EVENTTARGET': 'Template$TestCentreSearch1$SubRegionList'
}, make_request=False)
yield Task('parse', grab=grab, country=opt.text_content())
def task_parse(self, grab, task):
log.info('downloaded %s' % task.country)
city_gen = (x.text_content() for x in grab.css_list(".TestCentreSearchLabel+br+span"))
title_gen = (x.text_content() for x in grab.css_list(".TestCentreSearchTitle"))
id_gen = (x.attrib['href'][-36:] for x in grab.css_list(".TestCentreSearchLink"))
for x in zip(city_gen, title_gen, id_gen):
self.result[x[2]] = {
'country': task.country,
'city': x[0],
'name': x[1],
'id': x[2],
'price':'',
'currency':'',
'fee':''
}
yield Task('info', 'URL_URL=%s' % x[2], id=x[2])
def task_info(self, grab, task):
for label in grab.css_list(".TestCentreViewLabel"):
if label.text_content().strip()=="Test Fee:":
fees = label.getnext().text_content().strip()
self.result[task.id]['fee'] = fees
price = re.findall('\d[\d\., ]+\d',fees)
if price:
price = re.findall('\d[\d\., ]+\d',fees)[0]
self.result[task.id]['price'] = price.replace(' ','').replace(',','.')
currency = re.findall('[A-Z]{2,3}[$|€|£]?',fees)
if not currency:
currency = re.findall('[$|€|£]',fees)
if not currency:
currency = fees.replace(price,'').strip().replace(' ','')
if isinstance(currency,list):
currency = currency[0]
self.result[task.id]['currency'] = currency
#log.info(' %(price)s %(currency)s - %(fee)s ' % self.result[task.id])
break
def dump(self, path):
"""
Save result as csv into the path
"""
with open(path, 'w') as file:
file.write("ID;Country;State;City;Name;Price;Currency;Original Fee\n")
for test_center in sorted(self.result.values(), key=lambda x: "%(country)s%(city)s%(name)s" % x):
file.write(("%(id)s;%(country)s;;%(country)s;%(name)s;%(price)s;%(currency)s;%(fee)s\n" % test_center).encode('utf8'))
def main(choice):
parser, path, name = None, None, None
def run(name,parser,path):
log.info('Parsing %s...' % name)
parser.run()
parser.dump(path)
log.info('Parsing %s completed, data was dumped into %s' % (name, path))
log.info(parser.render_stats())
if choice == "NONE":
# DO NOTHING
# HERE I'D LIKE TO HAVE ANOTHER CALL TO ANOTHER THREADED FUNCTION
elif choice == "BUT1":
run('Function1',SPIDER1(),'C:\LOL\Output1'+DATE+'.csv')
因此,通过单击BUT1,我们运行包含在Module_1.py文件中的main(“BUT1”)函数,参数BUT1启动->运行('Function1',SPIDER1(),'C:\LOL\Output1'+DATE+'.csv'))
然后程序冻结,直到解析器完成工作:) 如果偶尔从BUT1函数调用
root.update()
,应该可以防止GUI冻结。您也可以通过一个固定间隔的python线程来实现这一点
例如,每0.1秒更新一次:
from threading import Thread
from time import sleep
self.updateGUIThread = Thread(target=self.updateGUI)
def updateGUI(self):
while self.updateNeeded
root.update()
sleep(0.1)
大函数完成后,您可以将
self.updateNeeded
设置为False。问题很简单:BUT1
在调用main
返回之前不会返回。只要main
(因此,BUT1
)没有返回,GUI就会被冻结
要使其工作,必须将
main
放在单独的线程中。如果main
所做的只是等待其他线程,那么它产生其他线程是不够的。非常有趣!我正在尝试从BUT1函数调用root.update(),然后返回,如果这个线程有效,则将其设置为关闭!非常感谢,看起来很有希望@野间町:这是非常危险的。你真的试过这个吗?如果它起作用了,并且最终没有在某个时候导致死锁或崩溃,我会感到惊讶。调用update
通常是一种不好的做法,从另一个线程调用似乎也相当危险。本质上,当您调用update
时,您正在创建第二个事件循环,如果在此过程中发生了导致再次调用此代码的事件,您可能会遇到真正的问题。@BryanOakley:我自己使用此循环时没有任何问题,您能解释一下您所指的“真正的问题”是什么吗?这难道不足以防止窗口失去响应吗?还有,你还有别的选择吗?@Junuxx:callingupdate
是危险的。实际上,它启动了一个新的事件循环。如果在处理事件的过程中重新调用了相同的代码,则最终会出现嵌套的事件循环。每个事件循环都将阻塞,直到其嵌套子项完成。另外,因为Tkinter不是线程安全的,所以从另一个线程调用任何Tkinter命令都是有风险的。另一种选择很简单,在另一个线程中运行耗时的过程,或者在另一个进程中运行可能更好的过程。但是,如果您确保更新间隔足够长,以便在再次调用之前有效地确定更新已完成,那么这将是安全的?我已经尝试过这个方法,但没有成功,我在线程方面非常新,如果您只有一个简单的示例,这将非常有用;)有什么想法吗?我试过了,但没有成功。。。帮助会很有帮助;)