如何使用PySimpleGUI结合线程实现循环的手动终止?
Hey there! I totally get the frustration with the beach ball on Mac—this happens because your heavy auto-sending loop is running in the main thread, which blocks PySimpleGUI's event handling (the thing that makes your window responsive). Let's fix this with threading, and add a proper manual stop feature too.
The Core Issue
PySimpleGUI relies on the main thread to process window events (clicks, updates, etc.). When you run that long loop with time.sleep() and pyautogui calls directly after clicking "Ok", you're hogging the main thread—so the window can't respond to any user input until the loop finishes, hence the freeze.
The Solution: Threading + Stop Flag
We'll split the work:
- Keep the GUI event loop in the main thread (where it belongs)
- Move the auto-sending logic to a separate worker thread
- Add a global "stop" flag to let the user terminate the loop at any time
Modified Full Code
Here's the updated version with all fixes. I've commented the key changes:
import webbrowser import pyautogui import random from random import choice import time import PySimpleGUI as sg import threading # Global flag to control the worker thread stop_thread = False font = ("Arial", 13) layout = [ [sg.Text('Select your .txt profiles file. Must be a list of URLs with each URL on a new line', font=("Arial", 16))], [sg.Combo(sorted(sg.user_settings_get_entry('-filenames-', [])), default_value=sg.user_settings_get_entry('-last filename-', ''), size=(50, 1), key='-FILENAME-', font="Arial, 16"), sg.FileBrowse(font="Arial, 16"), sg.B('Clear History', font="Arial, 16")], [sg.Button(' Ok ', bind_return_key=True, font="Arial, 16", button_color='green'), sg.Button('Cancel', font="Arial, 16")] ] window = sg.Window('Meetup Auto Message - Profiles selection', layout) lastFile = "" while True: event, values = window.read() if event in (sg.WIN_CLOSED, 'Cancel'): break if event == ' Ok ': # Update settings as before sg.user_settings_set_entry('-filenames-', list(set(sg.user_settings_get_entry('-filenames-', []) + [values['-FILENAME-'], ]))) sg.user_settings_set_entry('-last filename-', values['-FILENAME-']) lastFile=values['-FILENAME-'] break elif event == 'Clear History': sg.user_settings_set_entry('-filenames-', []) sg.user_settings_set_entry('-last filename-', '') window['-FILENAME-'].update(values=[], value='') window.close() # Process the profile links as before profileLink = open(lastFile).read().splitlines() profileLink = [item.replace("+","name=") for item in profileLink] newLinks = [item.replace("%","name=") for item in profileLink] nameList = [i.split("name=")[1] for i in newLinks] num_lines = sum(1 for line in open(lastFile)) BAR_MAX = num_lines # Worker function: runs in a separate thread to handle auto-messaging def auto_send_messages(message_options, names, links, window): global stop_thread for i, (name, link) in enumerate(zip(names, links)): # Check if user requested stop before each iteration if stop_thread: break # Your original messaging logic message = f"Hi {name}, \n{choice(message_options)}" webbrowser.open(link) time.sleep(15) pyautogui.press("tab") pyautogui.write(message, interval=random.uniform(0.03, 0.15)) with pyautogui.hold("command"): pyautogui.press("w") # Update progress bar (safe in this case, but for strict thread safety, use write_event_value) window['-PROG-'].update(i+1) # Long sleep after 10th message—check stop flag here too if i == 10: for _ in range(300): # Check stop every second instead of sleeping straight if stop_thread: break time.sleep(1) layout1 = [ [sg.Text('This program will take your messages, rotate them, add random delays and automatically message people from your profile list. You can now use your computer (or stop the program) while it runs!', font="Arial, 18")], [sg.Text('Enter your messages below just the content, we handle the "Hi Name" part.', font="Arial, 18")], [sg.Multiline(size=(100,10), key='textBox1', font=font)], [sg.Multiline(size=(100,10), key='textBox2', font=font)], [sg.Multiline(size=(100,10), key='textBox3', font=font)], [sg.Multiline(size=(100,10), key='textBox4', font=font)], [sg.Multiline(size=(100,10), key='textBox5', font=font)], [sg.Button(' Ok ', bind_return_key=True, font="Arial, 24", button_color='green'), sg.Button(' Cancel ', font="Arial, 24")], [sg.Text('Progress Bar', font="Arial, 15")], [sg.ProgressBar(BAR_MAX, orientation='h', size=(20,20), key='-PROG-')] ] window=sg.Window('Meetup Auto Message', layout1, element_justification='c') worker_thread = None while True: event, values = window.read() if event in (sg.WIN_CLOSED, ' Cancel '): # Set stop flag to terminate worker thread stop_thread = True # Wait for thread to finish (optional but clean) if worker_thread and worker_thread.is_alive(): worker_thread.join() break if event == ' Ok ': # Reset stop flag in case user runs multiple times stop_thread = False # Grab message options from GUI messageOptions = [values['textBox1'], values['textBox2'], values['textBox3'], values['textBox4'], values['textBox5']] # Start worker thread (daemon=True means it exits when main thread does) worker_thread = threading.Thread( target=auto_send_messages, args=(messageOptions, nameList, profileLink, window), daemon=True ) worker_thread.start() window.close()
Key Changes Explained
- Global Stop Flag:
stop_threadacts as a signal to the worker thread to exit its loop. We check this flag before every major operation, including during the 5-minute sleep (split into 1-second checks so the stop is responsive). - Worker Thread: The
auto_send_messagesfunction holds all your original messaging logic, but runs in a separate thread. This leaves the main thread free to handle GUI events. - Thread Safety: I split the 300-second sleep into 1-second chunks with stop checks—this ensures the program responds quickly when you click Cancel. For strict GUI thread safety, you could use
window.write_event_valueto send a progress update event to the main thread, but the directupdate()works here for simplicity. - Clean Exit: When you click Cancel or close the window, we set the stop flag and wait for the worker thread to finish before closing the window, preventing any leftover processes.
Testing This
- Run the program, select your profile file, enter messages, click Ok. The main window will stay responsive—you can move it, click Cancel at any time.
- Click Cancel, and the auto-sending loop will stop immediately (or within 1 second if it's in the long sleep).
This should eliminate the Mac beach ball freeze and give you the manual stop functionality you need!
内容的提问来源于stack exchange,提问作者dan.rad

