Python多线程处理网页截图调用join后程序冻结如何解决
问题根因
- 第一,
PhotoImage是GUI框架(大概率是Tkinter)的组件,所有GUI相关的操作都只能在主线程执行,你把self.graph = PhotoImage(...)放在子线程的takeScreenshot里运行,是程序冻结的核心原因,和join操作本身无关。 - 第二,你10个线程都读写同一个
screenshot.png文件,存在严重的文件读写冲突,很容易出现读半截文件、内容被覆盖的问题,哪怕你看到文件存在,实际也可能是其他线程生成的不完整文件。 - 第三,你的线程传参写法有问题,
threading.Thread的args要求传入元组,你写args = webpage会把字符串拆成单个字符逐个传参,takeScreenshot接收参数会直接报错。
修复方案
第一步:调整截图函数逻辑,子线程不碰GUI操作,每个线程生成独立的截图文件
把所有GUI相关代码(包括self.graph = PhotoImage(...))全部挪到主线程,子线程只负责截图、保存文件这类IO密集型操作,同时给每个线程的截图设置唯一文件名避免覆盖:
import threading import os from PIL import Image from selenium.webdriver.chrome.options import Options from selenium import webdriver from webdriver_manager.chrome import ChromeDriverManager # 子线程仅负责截图,返回生成的截图路径,不操作任何GUI组件 def takeScreenshot(webpage, save_path): ublockPath = r'C:\Users\Bassel Attia\Documents\Trading Core\1.37.2_0' chromeOptions = Options() chromeOptions.add_argument("--log-level=3") chromeOptions.add_argument('load-extension=' + ublockPath) # 每个线程单独创建driver,不要多线程共用driver driver = webdriver.Chrome(ChromeDriverManager().install(), chrome_options=chromeOptions) driver.get(webpage) driver.get_screenshot_as_file(save_path) image = Image.open(save_path) # 裁剪截图 area = (20, 290, 1250, 800) croppedImage=image.crop(area) croppedImage.save(save_path) image = Image.open(save_path) # 调整截图大小 newHeight = 300 newWidth = int(newHeight / image.height * image.width) resizedImage = image.resize((newWidth, newHeight)) resizedImage.save(save_path) image.close() driver.quit() return save_path
第二步:修改主线程逻辑,单独开监控线程等待所有任务完成
你需要输入过程不阻塞,输完10个地址后等待所有截图完成再展示,就不要在输入循环里直接join,单独开一个监控线程等待所有截图线程结束,结束后再通知主线程更新GUI,不会卡主程序:
threads=[] save_paths = [] n=0 while n<10: webpage = input("Enter the webpage") # 每个截图生成独立文件 save_path = f"screenshot_{n}.png" save_paths.append(save_path) # args参数必须传元组,单元素后面加逗号才是元组格式 thread = threading.Thread(target = takeScreenshot, args = (webpage, save_path)) thread.start() threads.append(thread) n +=1 # 监控线程专门等待所有截图任务完成,不阻塞主线程 def wait_all_done(): for t in threads: t.join() # 所有截图完成后通知主线程执行展示逻辑,不要在这个线程里操作GUI # 如果用Tkinter可以用after方法通知主线程更新:root.after(0, show_all_screenshots, save_paths) show_all_screenshots(save_paths) monitor_thread = threading.Thread(target=wait_all_done) monitor_thread.start() # 截图展示逻辑全部放在主线程执行 def show_all_screenshots(paths): for p in paths: self.graph = PhotoImage(file=p) # 剩下的图片展示逻辑写在这里
额外注意事项
- 所有GUI组件的创建、修改、赋值操作,必须全部放在主线程执行,子线程只能处理截图、下载这类IO操作。
- 如果用的是Tkinter框架,
PhotoImage对象要保留全局引用,不然会被垃圾回收导致图片显示空白。
内容的提问来源于stack exchange,提问作者asfa afs
相关产品推荐
相关产品推荐

