Python & Tkinter -> About calling a long running function that freeze the program

multithreading, parsing, python, tkinter

Solution

The problem is simple: `BUT1` won't return until the call to `main` returns. As long as `main` (and thus, `BUT1`) doesn't return, your GUI will be frozen.

For this to work you must put `main` in a separate thread. It's not sufficient that `main` spawns other threads if all it's doing is waiting for those threads.

Problem

Am a new in GUI programming and I am trying to make a GUI for one of my python parser. I know that : Tkinter is single threaded. Screen updates happen on each trip through the event loop. Any time you have a long running command you are preventing the event loop from completing an iteration, thus preventing the processing of events, thus preventing redraws. My program call a big function that takes about 5 minutes to be ran entirely. So I guess the only solution is tu use thread for the long running command. BUT, my long running command in already threaded so I don't really know how to proceed. --> As soon as I click on BUT1 in the GUI, the program freeze until the function is entirely done. I'd like to run this function in the backgroung, so the program will not freeze. --> I'm not looking for a complete solution but if someone can put me on a good track, it will be wonderful ! - Main.py -> The GUI - Module_1.py -> The function that we call by clicking on the button BUT1 Thank you in advance ! Here is Main.py --> the GUI ``` #!/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() ``` And here is Module_1.py --> contain the big function ``` #!/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') ``` So by clicking on BUT1, we run the main("BUT1") function contained in the Module_1.py file with argument BUT1 that launch -> run('Function1',SPIDER1(),'C:\LOL\Output1'+DATE+'.csv') And then the program freeze until the parser has finished is work .. :)

Original source