iT邦幫忙

0

concurrent.futures 使用問題

  • 分享至 

  • xImage

各位大大好!
先感謝各位在我上次發問迴圈問題時幫我解惑
目前程式可以在我打勾並開始執行後進行重複判斷圖片的功能
但是會造成視窗卡住,我有參考網路的作法加上平行任務的處理
不過執行時還是一樣會卡住完全不能操作視窗的按紐
有試過單獨按結束紐印出資料A=False,但是run2的迴圈好像沒有判定不成立而停止
請問是哪邊我沒有注意到呢? 再次感謝各位大大

import tkinter as tk # 導入TKinter
import pyautogui # 導入判斷GUI
import time # 導入時間
import cv2 # 導入照片處理
import numpy as np # 導入照片處理
import pyscreenshot as ImageGrab # 導入螢幕截圖
import threading # 導入多執行緒
from concurrent.futures import ThreadPoolExecutor# 導入平行任務


W = tk.Tk() # 建立主視窗 Frame
W.title('Monkey') # 設定視窗標題
window_width = W.winfo_screenwidth() # 取得電腦螢幕寬度
window_height = W.winfo_screenheight() # 取得電腦螢幕高度
width = 300 # 視窗大小
height = 300 # 視窗大小
left = int((window_width - width)/2) # 計算左上 x 座標
top = int((window_height - height)/2) # 計算左上 y 座標
W.geometry(f'{width}x{height}+{left}+{top}') # 視窗置中
W.iconbitmap('monkey.ico') # 檔案圖.ico
W.resizable(False, False ) # 設定 x 方向和 y 方向都不能縮放

var1=tk.BooleanVar()   # 綁定第 1 個選項的類別變數
#var1.set(0)                     # 預設不選取
A=var1.get()
A=True
def run():
    if var1.get() == 1: 
        run1()

def run1():
    im = ImageGrab.grab(bbox=(2105,1054,2204,1148)) # 擷取指定範圍畫面(x1,y1,x2,y2)
    im.save("b.png") # 儲存檔案
    image1 = cv2.imread("a.png") # 圖片1
    image2 = cv2.imread("b.png") # 圖片2
    diff = cv2.subtract(image1,image2) # 判斷兩張圖
    result = not np.any(diff)
    if result is True:
        print("Yes")
        time.sleep(1)
        return '1'
        
    else:
        cv2.imwrite("b.png",diff)
        print("NO")
        time.sleep(0.5)
        return '0'

def run2():
    global A
    A=var1.get()
    while var1.get():
        run()
    else:
        print("無勾選")

def run3():
    global A
    if A==True:
        var1.set(0)
        A=False
    else:
        A=True

executor = ThreadPoolExecutor()
a1 = executor.submit(run2)
a2 = executor.submit(run3)
executor.shutdown()

tk.Checkbutton(W, text="執行",variable=var1,command=run).pack(pady=100) # 打勾選取功能 

Start = tk.Button(W,text='開始', command=run2) # 按開始按鈕開始程式
Start.pack()

END2 = tk.Button(W,text='結束', command=run3) # 按結束按鈕結束程式
END2.pack()  



W.mainloop() # 執行主程式
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

2 個回答

1
re.Zero
iT邦研究生 5 級 ‧ 2023-02-05 12:30:40
最佳解答

在你的程式內, tkinterCheckbutton 按下、程式執行進入 while-loop 內不出來的話,程式不去跑 tkinter 程式碼, tkinter GUI 就不能動。
如果你要 tkinter GUI 能維持運作,且能控制 while-loop,我第一時間也是想到 並行執行

以下我用 Event Objects (用以應對執行緒的閒置)、你使用的 ThreadPoolExecutor (其實可單用 threading 模組,但你用了 ThreadPoolExecutor 我就加進去了)、搭配你的程式碼概要,做個「簡陋」的小範例給你參考:
(「簡陋」表示我省了很多安全性、維護性等語法,我自己看都有點怕,但寫太多又會變得很恐怖 + 我很懶……而且我不熟 tkinter,臨時只想的到這樣淺顯(?)的程式碼,後續就期望看有沒有人提出更便捷易讀的方法或程式碼吧~)

## Tested @[Python 3.10.9];
## 
import tkinter as tk
import time, threading
from concurrent.futures import ThreadPoolExecutor
## 
## myFlagsC: 全域變數用類別; 亦可用作執行緒間的通訊;
## Quit: 退出程式用旗標; Run1: Run1-main 的啟用旗標;
class myFlagsC: Quit, Run1 = False, False
## 
def myRun1():
	"""就只是 myRun1;"""
	while not myFlags.Quit:
		myEvent.wait() ## 停等 myEvent 狀態/旗標;
		while myFlags.Run1:
			print("myRun1...", end = ' ', flush = True)
			time.sleep(1.25)
			print("End;")
		myEvent.clear() ## 重設 myEvent 狀態;
## 
def myGUI1():
	"""就只是 myGUI1;"""
	def myRunCBClick():
		"""myRunCB 的 Click 事件用;"""
		myFlags.Run1 = var1.get() ## 規避(?) threading 與 tkinter 模組/物件之間的存取問題;
		myEvent.set() ## 觸發/設定 myEvent 狀態/旗標; 用以啟動閒置的 myRun1-Thread;
	## 
	def myQuit1():
		"""結束程式用;"""
		## 下 2 行用以結束 myRun1-Thread;
		myFlags.Quit, myFlags.Run1 = True, False
		if not myEvent.is_set(): myEvent.set()
		## 這邊我沒設置對 myRun1-Thread 的狀態檢查就關閉 tkinter GUI,請自行設置;
		## 不然會看到 tkinter GUI 已被關閉而 myRun1-Thread 還在跑一陣子的狀況,
		## 導致用戶誤會程式運行狀態。
		tRoot.destroy()
	## Setup a tkinter;
	tRoot = tk.Tk()
	## 下 1 行: 當 tkinter 被以 GUI 視窗右上角 [X] 關閉時,執行 myQuit1();
	tRoot.protocol("WM_DELETE_WINDOW", myQuit1)
	## 不設定 "WM_DELETE_WINDOW" 會導致:
	## 在 myRun1-Thread 還在執行時,就使用視窗右上角 [X] 或是 [Alt]+[F4] 而關閉 GUI 的話,
	## myRun1-Thread 會因沒被設置結束狀態而維持執行;
	## 
	(var1 := tk.BooleanVar()).set(False)
	## 
	tRoot.title('aTkinter')
	(myRunCB := tk.Checkbutton(
		tRoot, text = "執行", 
		variable = var1, 
		command = myRunCBClick
	)).pack()
	(myQuitB := tk.Button(
		tRoot,  text = '結束', 
		command = myQuit1
	)).pack()
	tRoot.mainloop()
## init-setup
myFlags = myFlagsC()
## Setup a Event: myEvent: 用以啟動 myRun1() 的 while(myFlags.Run1)-loop ;
myEvent = threading.Event()
## 
with ThreadPoolExecutor() as executor:
	executor.submit(myRun1)
	executor.submit(myGUI1)
## 
habi_tw iT邦新手 5 級 ‧ 2023-02-05 16:16:35 檢舉

感謝大大回覆~我依照大大的程式有試成功了,只是有幾個地方還不太了解為什麼需要這樣寫,我先自行找資料學習,如果還搞不懂再來問大大,感謝您

0
JamesDoge
iT邦高手 1 級 ‧ 2023-02-05 07:42:14

您可以使用 Thread 或 multiprocessing 模組來處理多重任務,以避免 UI 被卡住的情況。

import cv2
import numpy as np
import time
from threading import Thread

class ImageCompare:
    def __init__(self):
        self.A = False

    def compare(self, image1, image2):
        # 比較圖像
        # 返回True或False
        pass
    
    def run1(self):
        # 讀取圖像
        # 如果A=True,則重複判斷圖片
        while True:
            if self.A:
                # 讀取圖片1和圖片2
                image1 = cv2.imread("image1.jpg")
                image2 = cv2.imread("image2.jpg")

                result = self.compare(image1, image2)
                if result:
                    # 如果圖像不同,則輸出結果
                    print("Images are different")
                else:
                    # 如果圖像相同,則等待一段時間再次比較
                    time.sleep(1)

    def run2(self):
        while True:
            # 每隔一秒讀取一次A的值
            # 如果A=False,則退出迴圈
            if not self.A:
                break
            time.sleep(1)

    def start(self):
        # 開始執行重複判斷圖片的功能
        self.A = True
        thread1 = Thread(target=self.run1)
        thread2 = Thread(target=self.run2)
        thread1.start()
        thread2.start()

    def stop(self):
        # 停止重複判斷圖片的功能
        self.A = False

if __name__ == "__main__":
    image_compare = ImageCompare()
    image_compare.start()

範例程式僅供參考

habi_tw iT邦新手 5 級 ‧ 2023-02-05 16:15:01 檢舉

感謝大大回覆~我依照大大的資料在試看看。感謝您

我要發表回答

立即登入回答