2012-06-02 25 views
5

Soy un nuevo en la programación de GUI y estoy tratando de hacer una GUI para uno de mis analizadores de Python.Python & Tkinter -> Acerca de llamar a una función de larga ejecución que congela el programa

sé que:

  • Tkinter es de un solo subproceso. Las actualizaciones de pantalla ocurren en cada viaje a través del ciclo de eventos. Cada vez que tiene un comando de ejecución prolongada, impide que el ciclo de eventos complete una iteración, lo que impide el procesamiento de eventos, lo que evita el redibujado.

  • Mi programa llama a una función grande que tarda aproximadamente 5 minutos en ejecutarse por completo. Así que supongo que la única solución es usar hilo para el comando de larga ejecución. PERO, mi comando de ejecución larga ya está enhebrado, así que realmente no sé cómo proceder.

-> Tan pronto como haga clic en But1 en la GUI, la congelación del programa hasta que la función se realiza por completo. Me gustaría ejecutar esta función en el backgroung, por lo que el programa no se congelará.

-> No estoy buscando una solución completa, pero si alguien puede ponerme en una buena pista, ¡será maravilloso!

  • Main.py -> La interfaz gráfica de usuario
  • Module_1.py -> La función que llamamos haciendo clic en el botón But1

gracias de antemano!

Aquí es Main.py -> la 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() 

Y aquí es Module_1.py -> contiene la función de gran

#!/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') 

Así haciendo clic en But1, corremos el función principal ("BUT1") contenida en el archivo Module_1.py con el argumento BUT1 que inicia -> ejecutar ('Function1', SPIDER1(), 'C: \ LOL \ Output1' + DATE + '. csv') Y luego el programa congelar hasta que el analizador haya terminado es trabajo .. :)

Respuesta

3

El problema es simple: BUT1 no regresará hasta que regrese la llamada a main. Mientras no devuelva main (y por lo tanto, BUT1), su GUI se congelará.

Para que esto funcione, debe poner main en una secuencia separada. No es suficiente que main engendre otros hilos si todo lo que está haciendo es esperar esos hilos.

+0

Lo he intentado pero sin éxito, soy bastante nuevo en el manejo de cosas, si tiene solo un breve ejemplo, debería ser muy útil;) –

+0

¿Alguna idea? Lo he intentado pero sin éxito ... La ayuda será tan útil;) –

2

Si llama ocasionalmente al root.update() desde la función BUT1, eso evitará que la GUI se congele. También podría hacer eso desde un hilo de Python con un intervalo fijo.

Por ejemplo, actualizar cada 0,1 segundos:

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) 

Después de la gran función completa se puede establecer self.updateNeeded en Falso.

+0

¡Muy interesante! Estoy intentando llamar a root.update() desde la función BUT1 y volver a poner este hilo como cerrado si funciona. Muchas gracias, ¡parece muy prometedor! –

+1

@NokyamaShi: Esto es * muy * peligroso. ¿Has probado esto? Me sorprendería si funcionó y finalmente no causó un punto muerto o un choque en algún momento. Llamar 'update' es generalmente una mala práctica, y hacerlo desde otro hilo también parece bastante peligroso. Básicamente, estás creando un segundo ciclo de eventos cuando llamas a 'update', y si ocurre un evento durante el cual se llama nuevamente a este código, puedes tener problemas reales. –

+0

@BryanOakley: Lo he usado sin problemas, ¿podrías explicar a qué tipo de "problemas reales" te refieres? ¿No sería esto suficiente para evitar que la ventana dejara de responder? Además, ¿tienes una alternativa? – Junuxx

Cuestiones relacionadas