在pygtk应用程序上使用多线程以避免GUI冻结

时间:2014-01-13 21:01:24

标签: python multithreading pygtk

目前正在开发一个使用Glade for GTK + 3的相对复杂的GUI,并且没有理解连接按钮和信号的“how to-s”的问题,而且我对线程非常了解。

这是我的应用程序的简化版本的代码,其中线程正在运行:

#!/usr/bin/env python
import threading,logging
from gi.repository import Gtk,GObject,Gdk 
import os
import time
logging.basicConfig(level=logging.DEBUG,
                format='[%(levelname)s] (%(threadName)-10s) %(message)s',
                ) 
Path=os.path.dirname(os.path.realpath(__file__))
GObject.threads_init()

class MyThread(threading.Thread,GObject.GObject):
__gsignals__ = {
   "completed": (
            GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, [])
}

def __init__(self,*args):
    threading.Thread.__init__(self)
    GObject.GObject.__init__(self)
    self.cancelled = False
    self.name = args[0]
    self.setName("%s" % self.name)
def run(self):
    print "Running %s" % str(self)
    #actual work thread is doing----------
    time.sleep(3)
    #--------------------------------------
    logging.debug("Emiting signal...")
    GObject.idle_add(self.emit,"completed")
    logging.debug("Thread ending...")
    logging.debug("Done.")

class GUI(object):
def __init__(self):
    #build the GUI
    window = Gtk.Window()
    window.set_default_size(200, 200)
    vbox = Gtk.VBox(False, 5)
    hbox = Gtk.HBox(True, 5)
    self.spinner = Gtk.Spinner()
    self.button = Gtk.Button('Start')
    window.connect('destroy', Gtk.main_quit)
    self.button.connect('clicked', self.startAnimation)
    window.add(vbox)
    vbox.pack_start(Gtk.Label("Something"), False, False, 0)
    vbox.pack_start(self.spinner, True, True, 0)
    vbox.pack_end(hbox, False, False, 0)
    hbox.pack_start(self.button,True,True,0)
    window.show_all()
    Gtk.main()


def startAnimation(self,*args):
    self.button.set_sensitive(False)
    self.spinner.start()
    thread=MyThread("Tsat_thread")
    thread.connect("completed",self.completed_thread)
    thread.start()


def completed_thread(self,*args):
    #the subprocess call ended successfully so we can continue without problems
    #updating the result of the capture
    logging.debug("Function called at ending thread...")
    print "COMPLETED signal catched"
    self.spinner.stop()
    self.spinner.props.visible=False
    logging.debug("Done.")

print "Start of main GUI"
gui = GUI()
#print "mostro la finestra"  

创建一个线程来管理外部进程,否则会冻结接口。当线程完成时,会发出一个“已完成”信号并从MainThread中捕获(据我所知,这是唯一一个可以访问窗口并应用更改的人) 这是该计划的输出:

Start of main GUI 
Running MyThread(Tsat_thread, started 139636004558592)   
[DEBUG] (Tsat_thread) Emiting signal...   
[DEBUG] (Tsat_thread) Thread ending...   
[DEBUG] (Tsat_thread) Done.   
[DEBUG] (MainThread) Function called at ending thread...   
COMPLETED signal catched  
[DEBUG] (MainThread) Done.

当我在我的应用程序上尝试相同的方法时,“完成”信号不会从MainThread中捕获。我想知道问题出在哪里。代码的相关部分不起作用:

import subprocess,threading,logging
from gi.repository import Gtk,GObject,Gdk 
import os,datetime
import timeit
logging.basicConfig(level=logging.DEBUG,
                format='[%(levelname)s] (%(threadName)-10s) %(message)s',
                ) 

Path=os.path.dirname(os.path.realpath(__file__))
GObject.threads_init()
...
class MyThread(threading.Thread,GObject.GObject):
__gsignals__ = {
   "completed": (
            GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, [])
}

def __init__(self,*args):
    threading.Thread.__init__(self)
    GObject.GObject.__init__(self)
    self.cancelled = False
    self.name = args[0]
    self.interface=args[1]
    self.duration=args[2]
    self.conf_tstat=args[3]
    self.setName("%s" % self.name)
def run(self):
    print "Running %s" % str(self)
    #------------------------------------------------------------
    local_conf_file=self.conf_tstat.replace(Path,".")
    cmd = "dumpcap -a duration:"+self.duration+" -i "+self.interface+" -P -w - |tstat s capture -N "+local_conf_file+" stdin"
    time=datetime.datetime.now().strftime('%H_%M_%d_%b_%Y')
    with open("temp_log","wb") as logfile:
        logfile.write(time+".out\n")
        process = subprocess.Popen(cmd, shell=True, universal_newlines=True,  stdout=logfile)
        process.wait()
    #------------------------------------------------------------------
    logging.debug("Emiting signal...")
    GObject.idle_add(self.emit,"completed")
    logging.debug("Thread ending...")
    logging.debug("Done.")
...
# On button execute clicking 
def on_button_execute_clicked(self, *args):

    if (self.builder.get_object("radio_online").get_active()):
        print "#Online"
        self.builder.get_object("spinner1").start()
        self.builder.get_object("spinner1").props.visible=True
        entries=self.get_entries()

        thread=MyThread("Tsat_thread",entries[0],entries[1],entries[2])
        thread.connect("completed",self.completed_tstat)
        thread.start()

    else:
        print "#Offline"
        ...
...
#function called after Tstat_thread finishes
def completed_tstat(self,*args):
    #the subprocess call ended successfully so we can continue without problems
    #updating the result of the capture
    logging.debug("Function called at ending thread...")
    print "COMPLETED signal catched"
    with open("temp_log","rb") as logfile:
        capture_no=logfile.readline()
    capture_no=capture_no.strip('\n')   
    self.builder.get_object("capture_entry").set_text(Path+"/capture/"+capture_no)
    self.list_of_entries = ["capture_entry","output_entry"]
    entries = self.get_entries()
    if entries[1]=='':
        #default output folder
        self.builder.get_object("output_entry").set_text(Path+"/output")
        entries= self.get_entries()

    #deleting possible results from a previous capture
    rm="rm -f "+entries[1]+"/log_* "+entries[1]+"/outLog.*"
    subprocess.call(rm.split(),shell=False)

    #triminng the info from the tstat log files
    command="python ./python/trimInfo.py -t "+entries[0]+ "/log_tcp_complete, "+entries[0]+ "/log_tcp_nocomplete -u "+entries[0]+"/log_udp_complete -o "+entries[1]
    process=subprocess.Popen(command.split(),shell=False,stdout=subprocess.PIPE)
    process.wait()
    returncode=process.poll()
    if(returncode==0):
        self.builder.get_object("entry_output_folder").set_text(entries[1])
        self.builder.get_object("button_show_out_folder").props.sensitive=True
        self.show_info("Capture+Transformation of traffic data ended successfully!")
        self.set_page_complete()
    else:
        self.my_log+=returncode
        self.handle_program_error(my_log)

    self.builder.get_object("spinner1").stop()
    self.builder.get_object("spinner1").props.visible=False
    logging.debug("Done.")

completed_tstat是回调,但我的应用程序从未调用过。我尝试多线程时应用程序的输出如下:`

Start of main GUI
#Online
device_entry= 'wlan0'
time_spinbutton= '10'
tstat_conf_entry= './codicePippo/tstat-2.3/tstat-conf/net.private'
capture_entry= './capture/'
output_entry= './output'
Running <MyThread(Tsat_thread, started 140689548355328)>
Capturing on wlan0
File: -

Packets: 6 
Packets: 7 
Packets: 10 
Packets: 13 
Packets: 14 
Packets: 15 
Packets: 20 
Packets: 23 
Packets: 24 
Packets captured: 24
Packets received/dropped on interface wlan0: 24/0 (100.0%)

WARN: This udp flow is neither incoming nor outgoing: src - 192.168.1.27; dst - 192.168.1.255!
[DEBUG] (Tsat_thread) Emiting signal...
[DEBUG] (Tsat_thread) Thread ending...
[DEBUG] (Tsat_thread) Done.

1 个答案:

答案 0 :(得分:2)

我建议避免使用线程和子进程,我前面发布的这个答案使用的是异步调用,它是为pygobject(内省)编写的,但它应该很容易移植到pygtk

button Stop/Cancel progressBar from subprocess PYGTK