1. 程式人生 > >利用多執行緒解決Tkinter,在button事件中執行高io,高耗時操作,視窗無響應問題

利用多執行緒解決Tkinter,在button事件中執行高io,高耗時操作,視窗無響應問題

    昨天利用python自己寫了一個微型伺服器,突發奇想用Tkinter寫一個GUI介面,並將監聽開始的函式繫結到其中的一個button上,但是當我點選開始服務button時,視窗立馬陷入無響應狀態。搜尋一番發現,是伺服器函式中的while迴圈阻塞了GUI的響應,同理而言,做高I/O操作也一樣會面臨這種情況,同時用一個執行緒處理GUI響應與功能函式極易導致資源緊缺,進而導致視窗無響應。

解決方法:

(1)多執行緒方法

我們利用多執行緒來分別執行GUI和功能函式是一個非常好的方法,好在python中多執行緒是極其容易使用的。多執行緒方法如下:

import threading
t= threading.Thread(target = func)
t.start()

先引入threading包,利用threading.Thread建立執行緒,func是你需要執行的函式。

(2)分類GUI和功能類

你需要將GUI與功能類分離,具體示例程式碼如下:

from socket import *
from tkinter import *
import time
import threading
import re
#GUI視窗類
class Control():
    #定義GUI介面
    def __init__(self, master, fuc):
        self.parent = master
        self.parent.title("伺服器配置器")
        self.frame = Frame(self.parent)
        self.frame.pack(fill=BOTH, expand=3)
        self.parent.resizable(width=False, height=False)

        self.label = Label(self.frame, text="埠設定")

        self.label.grid(row=0, column=0, sticky=E)
        self.entry = Entry(self.frame)
        self.entry.grid(row=0, column=1, sticky=W + E)

        self.label2 = Label(self.frame, text="訪問日誌")
        self.label2.grid(row=1, column=0,  rowspan=6, sticky=W + N)
        self.list = Listbox(self.frame, width = 100,height = 40)
        self.list.grid(row=3, column=1,  rowspan=6, sticky=E + S)

        self.stBtn = Button(self.frame, text="開始服務", command=fuc)
        self.stBtn.grid(row=10, column=1, sticky=W + E)
#具體功能類
class ThreadClient():
    def __init__(self, master):
        self.master = master 
        self.gui = Control(master, self.starting) #將我們定義的GUI類賦給服務類的屬性,將執行的功能函式作為引數傳入

    #獲取請求日誌
    def get_log(self, addr, message):
        timenow = time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time()))
        way = message.decode('utf-8').split()[0]
        resorce = message.decode('utf-8').split()[1][1:]
        info = timenow+": " + "client:" + str(addr) + " way: "+ way + " resorce: " + resorce
        return info


    #監聽方法
    def start_listen(self):
        host = ''   #對應本機所有ip地址
        port = int(self.gui.entry.get()) #TCP socket埠
        if not port:
            port = 8086
        address = (host, port)
        serverSocket = socket(AF_INET, SOCK_STREAM) #建立TCP socket
        serverSocket.bind(address)  #繫結地址
        serverSocket.listen(1)  #開始監聽
        keepalive = False
        while True:
            try:
                # if not keepalive:
                connectionSocket, clientAddr = serverSocket.accept()    #獲取「連線套接字」
                print("create------")
                print("recv the http request")
                message = connectionSocket.recv(1024)   #獲得http報文
                print("request", message.decode('utf-8'))
                info = self.get_log(clientAddr, message)
                self.gui.list.insert(END, info)
                filename = message.split()[1]   #獲得URI,去掉首部'/'就是檔名
                keepalive = len(re.findall(r'keep-alive', message.decode('utf-8')))
                f = open(filename[1:], 'rb')

                outputdata = f.readlines()  #逐行讀出檔案內容並存到list中
                connectionSocket.send('HTTP/1.1 200 OK\r\n\r\n'.encode('utf-8'))    #發response行

                for i in range(0, len(outputdata)):
                    connectionSocket.send(outputdata[i])   #把檔案各行資料塞到response中 send只能是string型別
                # if not keepalive:
                connectionSocket.close()    #關閉資料連線

            except IOError:
                connectionSocket.send("404 not found".encode('utf-8'))  #檔案不存在時異常處理
                connectionSocket.close()
        serverSocket.close()
    #為方法開一個單獨的執行緒
    def starting(self):
        self.thread = threading.Thread(target = self.start_listen)
        self.thread.start()


if __name__ == '__main__':

    root = Tk()
    tool = ThreadClient(root)
    root.mainloop()