Python GUI Cookbook —— 线程与网络
2018-01-21 09:17
525 查看
原文链接:Python Cookbook —— 线程与网络
创建线程、队列和TCP/IP套接字
当进程被创建时,进程会自动创建一个主线程来运行该应用程序,被称为单线程应用程序。
对于我们的 Python GUI,单线程应用程序将导致 GUI 在调用长运行时间的任务时(例如点击了具有几秒钟睡眠时间的按钮)变冻结。为了保持 GUI 的响应,我们需要使用多线程。 我们也可以通过创建多个 GUI 实例来构成(正如在任务管理器中所见的)多个进程。
在设计上,进程之间彼此隔离、不共享公共数据。为了在独立的两个进程之间进行通信,需要使用先进的进程间通信(Inter Process
Communication,IPC)技术。而另一方面,线程则共享公用数据、代码和文件,这使得同一进程中的线程间通信比使用 IPC 时更容易。
首先导入
在类中添加一个方法以创建线程
调用该方法
现在运行代码 … … 好吧,什么都没发生。
这里我们使用
在按钮的调用方法中添加一个带有睡眠时间的循环,会导致 GUI 无响应。
不像常规的函数和方法,这里我们需要
在
可以发现我们的 GUI 又能响应了。
首先,通过对线程构造函数添加
这时,如果我们提前结束 GUI,我们就会得到以下错误:
通过将线程转换为守护进程能解决这个问题。
从
创建队列实例
在按钮点击事件中调用该方法
运行代码,会导致以下结果:
随着代码的运行,我们的 GUI 被冻结了。为了解决该问题,我们需要在它自己的线程中调用该方法。
将 GUI widget 从表达业务逻辑的功能中分离出来
导入另外的模块
修改代码
使用内置对话框浏览硬盘
让文本框只读、指定默认的进入位置
首先,在
下面使用 tkinter 内置的对话框
可以为文本框设置默认值
获取模块完整路径
将
本例可以用来在
源代码点此查看
修改
在
导入
源代码点此查看
参考文献
Python GUI Programming Cookbook - Second Edition by Burkhard A. Meier
创建线程、队列和TCP/IP套接字
当进程被创建时,进程会自动创建一个主线程来运行该应用程序,被称为单线程应用程序。
对于我们的 Python GUI,单线程应用程序将导致 GUI 在调用长运行时间的任务时(例如点击了具有几秒钟睡眠时间的按钮)变冻结。为了保持 GUI 的响应,我们需要使用多线程。 我们也可以通过创建多个 GUI 实例来构成(正如在任务管理器中所见的)多个进程。
在设计上,进程之间彼此隔离、不共享公共数据。为了在独立的两个进程之间进行通信,需要使用先进的进程间通信(Inter Process
Communication,IPC)技术。而另一方面,线程则共享公用数据、代码和文件,这使得同一进程中的线程间通信比使用 IPC 时更容易。
创建多线程
为了保持 GUI 响应,创建多线程时必要的。多线程运行在相同的计算机进程内存空间,不需要写复杂的 IPC 代码。首先导入
threading模块
from tkinter import ttk from tkinter import scrolledtext from tkinter import messagebox as msg from tkinter import Spinbox from tkinter import Menu from time import sleep from threading import Thread [...]
在类中添加一个方法以创建线程
def method_in_a_thread(self): print('Hi, How are you?')
调用该方法
app = App() # Running methods in Threads run_thread = Thread(target=app.method_in_a_thread) app.win.mainloop()
现在运行代码 … … 好吧,什么都没发生。
开始一个线程
当我们在不使用线程时,调用一些有睡眠(sleep)的函数或方法的 GUI 会发生什么?这里我们使用
sleep来模拟实际应用中等待 web 服务器和数据库响应、大文件传输或复杂的计算等任务。
在按钮的调用方法中添加一个带有睡眠时间的循环,会导致 GUI 无响应。
def click_me(self): self.action.configure(text='Hello '+self.name.get()+' '+ self.number_chosen.get()) # Non-threaded code with sleep freezes the GUI for idx in range(10): sleep(5) self.scrol.insert(tk.INSERT, str(idx)+'\n')
不像常规的函数和方法,这里我们需要
start一个方法才能开启它自己的线程。
[...]
def method_in_a_thread(self): print('Hi, How are you?')
# Running methods in Threads
def create_thread(self):
self.run_thread = Thread(target=self.method_in_a_thread)
print(self.run_thread)
self.run_thread.start()
def click_me(self):
self.action.configure(text='Hello '+self.name.get()+' '+
self.number_chosen.get())
self.create_thread()
# # Non-threaded code with sleep freezes the GUI
# for idx in range(10):
# sleep(5)
# self.scrol.insert(tk.INSERT, str(idx)+'\n')
[...]
在
method_in_a_thread中添加一些
sleep,来验证一下是不是真的解决了我们的问题:
def method_in_a_thread(self): print('Hi, How are you?')
for idx in range(10):
sleep(5)
self.scrol.insert(tk.INSERT, str(idx)+'\n')
可以发现我们的 GUI 又能响应了。
停止线程
直觉上来说,如果有start()方法那么就会有相应的
stop()方法,然而并没有。因此需要将线程作为后台任务运行(守护进程,daemon),当关闭主线程(GUI)时,所有的守护进程也将自动停止。
首先,通过对线程构造函数添加
args参数,我们可以给线程的方法传入参数。
def method_in_a_thread(self, num_of_loops=10): print('Hi, How are you?') for idx in range(num_of_loops): sleep(1) self.scrol.insert(tk.INSERT, str(idx)+'\n') sleep(1) print('method_in_a_thread():', self.run_thread.isAlive()) # Running methods in Threads def cr 4000 eate_thread(self): self.run_thread = Thread(target=self.method_in_a_thread, args=[8]) print(self.run_thread) self.run_thread.start() print('createThread():',self.run_thread.isAlive())
这时,如果我们提前结束 GUI,我们就会得到以下错误:
<Thread(Thread-1, initial)> Hi, How are you? createThread(): True Exception in thread Thread-1: Traceback (most recent call last): File "C:\Python36\lib\threading.py", line 916, in _bootstrap_inner self.run() File "C:\Python36\lib\threading.py", line 864, in run self._target(*self._args, **self._kwargs) File "multiple_threads.py", line 169, in method_in_a_thread self.scrol.insert(tk.INSERT, str(idx)+'\n') File "C:\Python36\lib\tkinter\__init__.py", line 3266, in insert self.tk.call((self._w, 'insert', index, chars) + args) RuntimeError: main thread is not in main loop
通过将线程转换为守护进程能解决这个问题。
# Running methods in Threads def create_thread(self): self.run_thread = Thread(target=self.method_in_a_thread, args=[8]) print(self.run_thread) self.run_thread.setDaemon(True) self.run_thread.start() print('createThread():',self.run_thread.isAlive())
使用队列
当接收到要处理和显示的最终的结果时,使用多线程在队列中来完成分配的任务是非常有用的。数据以先入先出(FIFO)的方式工作。从
queue模块导入
Queue类
import tkinter as tk from tkinter import ttk from tkinter import scrolledtext from tkinter import messagebox as msg from tkinter import Spinbox from tkinter import Menu from time import sleep from threading import Thread from queue import Queue [...]
创建队列实例
def use_queues(self): gui_queue = Queue() print(gui_queue) for idx in range(10): gui_queue.put('Message from a queue: '+str(idx)) while True: print(gui_queue.get())
在按钮点击事件中调用该方法
def click_me(self): self.action.configure(text='Hello '+self.name.get()+' '+ self.number_chosen.get()) self.create_thread() self.use_queues()
运行代码,会导致以下结果:
随着代码的运行,我们的 GUI 被冻结了。为了解决该问题,我们需要在它自己的线程中调用该方法。
# Running methods in Threads
def create_thread(self):
self.run_thread = Thread(target=self.method_in_a_thread, args=[8])
print(self.run_thread)
self.run_thread.setDaemon(True)
self.run_thread.start()
# print('createThread():',self.run_thread.isAlive())
# start queue in its own thread
write_thread = Thread(target=self.use_queues, daemon=True)
write_thread.start()
def use_queues(self): gui_queue = Queue() print(gui_queue) for idx in range(10): gui_queue.put('Message from a queue: '+str(idx)) while True: print(gui_queue.get())
def click_me(self):
self.action.configure(text='Hello '+self.name.get()+' '+
self.number_chosen.get())
self.create_thread()
在不同的模块之间传递队列
模块化设计使得代码能够重用且提高了代码的可读性将 GUI widget 从表达业务逻辑的功能中分离出来
导入另外的模块
import Queues as bq
Queues.py
def write_to_scrol(inst): print('hi from Queue', inst) for idx in range(10): inst.gui_queue.put('Message from a queue: ' + str(idx)) inst.create_thread(6)
修改代码
[...] def __init__(self): # Create instance self.gui_queue = Queue() self.win = tk.Tk() # Add title self.win.title("Python GUI") self.create_widgets() [...] # Running methods in Threads def create_thread(self, num=3): self.run_thread = Thread(target=self.method_in_a_thread, args=[num]) print(self.run_thread) self.run_thread.setDaemon(True) self.run_thread.start() # print('createThread():',self.run_thread.isAlive()) # start queue in its own thread write_thread = Thread(target=self.use_queues, daemon=True) write_thread.start() def use_queues(self): # gui_queue = Queue() # print(gui_queue) # for idx in range(10): # gui_queue.put('Message from a queue: '+str(idx)) while True: print(self.gui_queue.get()) def click_me(self): print(self) self.action.configure(text='Hello '+self.name.get()+' '+ self.number_chosen.get()) # self.create_thread() bq.write_to_scrol(self) [...]
使用对话框 widget 将文件复制到网络
将文件从本地硬盘复制到网络位置使用内置对话框浏览硬盘
让文本框只读、指定默认的进入位置
首先,在
Tab2中添加两个按钮和文本框
# Create Manage Files Frame mngFilesFrame = ttk.LabelFrame(tab2, text=' Manage Files: ') mngFilesFrame.grid(column=0, row=1, sticky='WE', padx=10, pady=5) # Add Widgets to Manage Files Frame lb = ttk.Button(mngFilesFrame, text='Browse to File...', command=self.getFileMame) lb.grid(column=0, row=0, sticky=tk.W) cb = ttk.Button(mngFilesFrame, text='Copy File to: ', command=self.copyFile) cb.grid(column=0, row=1, sticky=tk.W) file = tk.StringVar() self.entryLen = scrol_w self.fileEntry = ttk.Entry(mngFilesFrame, width=self.entryLen, textvariable=file) self.fileEntry.grid(column=1, row=0, sticky=tk.W) logDir = tk.StringVar() self.netwEntry = ttk.Entry(mngFilesFrame, width=self.entryLen, textvariable=logDir) self.netwEntry.grid(column=1, row=1, sticky=tk.W) # Add some space around each label for child in mngFilesFrame.winfo_children(): child.grid_configure(padx=6, pady=6)
下面使用 tkinter 内置的对话框
from tkinter import filedialog as fd from os import path
def getFileMame(self): print('hello from getFileMame') fDir = path.dirname(__file__) fname = fd.askopenfilename(parent=self.win, initialdir=fDir)
可以为文本框设置默认值
self.name_entered = ttk.Entry(mighty, width=24, textvariable=self.name) self.name_entered.grid(column=0, row=1, sticky='W') self.name_entered.delete(0, tk.END) self.name_entered.insert(0, '< default name>')
获取模块完整路径
# Module level GLOBALS fDir, _ = path.split(path.realpath(__file__)) netDir = fDir + '\\Backup' class App(): def __init__(self): # Create instance self.gui_queue = Queue() self.win = tk.Tk() # Add title self.win.title("Python GUI") self.create_widgets() self.defaultFileEntries() def defaultFileEntries(self): self.fileEntry.delete(0, tk.END) self.fileEntry.insert(0, fDir) if len(fDir) > self.entryLen: self.fileEntry.config(width=len(fDir) + 3) self.fileEntry.config(state='readonly') self.netwEntry.delete(0, tk.END) self.netwEntry.insert(0, netDir) if len(netDir) > self.entryLen: self.netwEntry.config(width=len(netDir) + 3)
将
Tab2设置为默认显示页
# self.name_entered.focus() tabControl.select(1)
本例可以用来在
backup文件夹备份代码,如果不存在则使用
os.makedirs创建
if not path.exists(netDir): makedirs(netDir, exist_ok=True)
源代码点此查看
使用 TCP/IP 通过网络通信
创建TCP_Server模块
from socketserver import BaseRequestHandler, TCPServer class RequestHandler(BaseRequestHandler): # override base class handle method def handle(self): print('Server connected to: ', self.client_address) while True: rsp =self.request.recv(512) if not rsp: break self.request.send(b'Server received: ' + rsp) def start_server(): server = TCPServer(('', 24000), RequestHandler) server.server_forever()
修改
Queues模块
# Using TCP/IP from socket import socket, AF_INET, SOCK_STREAM def write_to_scrol(inst): print('hi from Queue', inst) sock = socket(AF_INET, SOCK_STREAM) sock.connect(('localhost', 24000)) for idx in range(10): sock.send(b'Message from a queue: ' + bytes(str(idx).encode())) recv = sock.recv(8192).decode() inst.gui_queue.put(recv) inst.create_thread(6)
在
App类的初始化过程中,让 TCP server 在自己的线程中开始
[...] import Queues as bq from TCP_Server import start_server [...] class App(): def __init__(self): # Create instance self.gui_queue = Queue() self.win = tk.Tk() # Add title self.win.title("Python GUI") self.create_widgets() self.defaultFileEntries() # Start TCP/IP server in its own thread svrT = Thread(target=start_server, daemon=True) svrT.start() [...] def use_queues(self): while True: q_item = self.gui_queue.get() self.scrol.insert(tk.INSERT, q_item+'\n') print(q_item) [...]
使用 urlopen 从网站读取数据
创建新的URL模块
from urllib.request import urlopen link = 'http://python.org/' def get_html(): try: http_rsp = urlopen(link) print(http_rsp) html = http_rsp.read() print(html) html_decoded = html.decode() print(html_decoded) except Exception as ex: print('*** Failed to get Html! ***\n\n' + str(ex)) else: return html_decoded if __name__ == '__main__': get_html()
导入
URL模块
[...] import Queues as bq from TCP_Server import start_server import URL as url [...] def click_me(self): print(self) self.action.configure(text='Hello '+self.name.get()+' '+ self.number_chosen.get()) # self.create_thread() bq.write_to_scrol(self) sleep(2) html_data = url.get_html() print(html_data) self.scrol.insert(tk.INSERT, html_data)
源代码点此查看
参考文献
Python GUI Programming Cookbook - Second Edition by Burkhard A. Meier
相关文章推荐
- Python GUI Cookbook —— Matplotlib 图表
- Python GUI Cookbook —— 布局管理
- Python GUI Cookbook —— 创建 GUI 窗体并添加 Widgets
- python cookbook 3rd Python Cookbook(第3版)中文版:12.12 使用生成器代替线程
- Python GUI Cookbook —— 定制 widgets
- Python Cookbook 4.1 复制(拷贝)对象(浅复制和深复制)
- python cookbook第一章
- sampling brief —— python data science cookbook
- Python Cookbook 第二版 汉化版 [Recipe 1.7] 以单词或字符为单位对字符串进行反序排列
- Python Cookbook学习记录 ch4_6/7_2013/11/2
- Python Cookbook 4.1 复制(拷贝)对象(浅复制和深复制)
- Recipe 1.1. Processing a String One Character at a Time(Python Cookbook)
- Python Cookbook 2——Python技巧
- Python cookbook(数据结构与算法)字典相关计算问题示例
- python 随机数(python cookbook)
- Python cookbook(数据结构与算法)对切片命名清除索引的方法
- 【python cookbook】 替换字符串中的子串
- 《Modern Python Cookbook》(Python编程范例)笔记——1.4 浮点数、小数、分数
- 《Modern Python Cookbook》(Python编程范例)笔记1.2 命名
- Python Cookbook 第二版 汉化版 [00-2-Preface] Part 2