我正在嘗試使用 tkinter 做一個應用程式,該應用程式在輸入框中輸入內容然后單擊“加載”按鈕后從某些互聯網資源獲取資訊。
按鈕函式從輸入框中讀取內容字串。加載后,它會從互聯網上檢索一些資訊(這是阻塞的),然后用這些資訊更新一些標簽。
顯然,當單擊加載按鈕時,由于請求阻塞了程式的流程,應用程式會凍結一微秒。檢索到資訊并更新標簽后,其他一些標簽將需要不斷地從互聯網上檢索資料。為此,我讓他們做了一些這樣的事情:
注意:列印陳述句是為了測驗而完成的,所以我可以在控制臺上看到它們
def update_price_label(self):
# TODO fix .after() duplicate
print("Updating stuff")
price = self.get_formatted_price(self.stuff) # this is another function being called, passing an argument of the stuff that has been loaded by the load button, this function returns price
self.PriceValue.configure(text=price) # updates the price label with the price obtained from the function above
self.PriceValue.after(1000, self.update_price_label) # sets a scheduler to run this function to update the price label each second
上面有一個函式,在點擊“加載”時呼叫一個需要一直更新的標簽,這個函式呼叫另一個函式,接收一個引數,然后回傳價格。after()然后更新標簽,然后使用priceValue 標簽控制元件的方法將其安排在無限回圈中。這樣,價格隨時更新
def get_formatted__price(self, stuff):
price = RETRIEVE PRICE # this is not a real function, but this is the request being done to the server to get the price
return f"{price:.12f} # sets decimal numbers
此函式由 呼叫update_price_label(),接收引數并回傳價格
如您所見,我將標簽更新功能與實際從服務器檢索資訊的實際功能分開。所以第一個函式負責呼叫另一個函式來檢索資訊,更新標簽,然后使用after()每個標簽小部件的方法再次重新調度自己。
像這樣的 5 個功能需要更新應用程式上的多個標簽,連接到不同的來源并保持資訊最新。所有這些都使用after()相同的時間間隔(1 秒)安排并運行。
顯然,由于不使用任何執行緒,應用程式在請求資訊時會凍結很多,因為它們本質上是阻塞的。
所以我需要實作執行緒,或者任何形式的并發。而且我找不到任何好的教程,或者至少適合我對定期從來源獲取資訊的應用程式的需求。
我仍然總體上掌握執行緒和并發的概念,也許還有其他方法,例如異步或其他我還不知道的并發方法,可能更適合。但 Threading 似乎是 Tkinter 最常使用的一種。
我假設這些請求函式中的每一個都需要一個執行緒。像這樣:
get_formatted_price_thread = Thread(target=get_formatted_price, args=(stuff), daemon=True) # calling a thread with an argument on the function and setting daemon to true, so it closes when I close the app
因此,我嘗試在其中一個上創建執行緒作為示例,但我發現了一些限制,例如:
無法get_formatted_price()直接獲取函式的回傳值。所以另一種方法可能只是讓執行緒中的函式更改標簽值。或者將整個標簽更新功能包裝在一個執行緒中。但正如我到處讀到的那樣,Tkinter 不是執行緒安全的。這意味著更新 tkinter 小部件可能在某些作業系統上運行良好,但在其他作業系統上卻不行。
除此之外,我似乎還在努力研究如何將這個應用程式的結構轉變為適用于執行緒或任何型別的并發的東西。因為我需要在檢索到新資訊后立即更新所有標簽。
我的另一個擔憂是執行緒是由作業系統控制的。因為它決定何時啟動執行緒,以及在獲取資料時這會如何影響我的應用程式性能。
我還檢查了佇列和佇列庫,但我不確定這是否對我有幫助,因為每個價格更新都會由執行緒放入佇列中,并由標簽小部件檢索。但是當佇列獲取佇列的第一個元素時,資訊可能會過時。
所以我的問題是我需要在這里改變什么來實作我所需要的。如果執行緒是我需要繼續進行的,或者我可能需要嘗試另一種方法。
任何可以滿足我需要的應用程式示例源代碼將不勝感激。畢竟,檢索資訊并讓小部件與該資訊保持同步應該是一個非常常見的用例。
我想到的另一種方法是創建資料結構,例如 Python 字典或物件。每個服務器獲取函式將作為守護行程在一個無限回圈中的執行緒中運行,并將寫入字典。然后標簽小部件更新功能,因為它們被調度,將讀取字典上的資料并相應地更新標簽。但我認為這種方法可能會很混亂,并且更新標簽與字典上的資訊可能會有延遲,除非after()設定了較小的調度程式計時器。或者默認情況下所有解決方案都是混亂的
謝謝你。
uj5u.com熱心網友回復:
我會通過創建一個資料結構,創建一個可以根據資料結構中的當前值更新顯示的函式,然后系結到呼叫該函式的事件來解決這個問題。然后,創建一個執行緒來更新此資料結構并在資料更改時發出事件。
這是一個人為的示例,它每秒呼叫一次 Web 服務并使用時間和時區資訊更新一個簡單的資料結構。每當資料更改時,它都會發出一個<<Tick>>觸發顯示更新的事件。
我不是撰寫執行緒 tkinter 代碼的專家,據我了解,除了在極少數情況下,在創建小部件的執行緒之外的執行緒中運行任何 tkinter 代碼是不安全的。一個例外是附加執行緒生成事件是安全的,因為事件本身在主 GUI 執行緒中處理。我猜呼叫該winfo_exists函式也是安全的,因為它不會修改任何內部資料結構。
此示例會在 10 秒后自行終止,以免錘擊服務器太久。
import requests
import tkinter as tk
from tkinter.font import Font
from threading import Thread
import time
class ThreadedClock(tk.Frame):
data = {"time": "", "tz": ""}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.time_label = tk.Label(self, width=12, font=Font(size=32))
self.tz_label = tk.Label(self, text="GMT")
self.time_label.pack(side="top", fill="x")
self.tz_label.pack(side="top", fill="x")
# call the refresh function on every <<Tick>> event
self.bind("<<Tick>>", self.refresh)
# start a thread to update the data and generate <<Tick>> events
self.thread = Thread(target=self.get_data, daemon=True)
self.running = True
self.thread.start()
def get_data(self):
while self.winfo_exists():
now = time.time()
response = requests.get(
"https://timeapi.io/api/Time/current/zone?timeZone=GMT"
)
t = response.json()
timestr = f"{t['hour']:02}:{t['minute']:02}:{t['seconds']:02}"
self.data = {"time": timestr, "tz": t["timeZone"]}
self.event_generate("<<Tick>>")
delta = time.time() - now
time.sleep(delta)
def refresh(self, event=None):
self.time_label.configure(text=self.data["time"])
self.tz_label.configure(text=f"timezone: {self.data['tz']}")
if __name__ == "__main__":
root = tk.Tk()
root.after(10000, root.destroy)
clock = ThreadedClock(root)
clock.pack(fill="both", expand=True)
root.mainloop()
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/527245.html
上一篇:在腳本中實作多執行緒/并行處理
