I have a button that is supposed to stop a thread that is running a server function on another Python file. The solutions I've tried are as follows:
Solution #1: Threading.Event
mainmenu.py
import server as serv #as in server.py
import socket
from threading import Thread
import customtkinter as cust
class GUI2(cust.CTk): #second window; not the root
def __init__(self):
self.master2 = cust.CTkToplevel()
self.master2.title("Admin/Host Lobby")
self.master2.geometry(f"{906}x{400}")
self.master2.protocol("WM_DELETE_WINDOW", self.leavewindow)
self.leave = cust.CTkButton(self.master2, text = "Leave", fg_color = "Red", text_color = "White", hover_color = "Maroon", command = lambda: self.leavewindow())
self.leave.grid(row = 0, column = 0, sticky = "n", padx = 1, pady = 1)
self.thread = Thread(target = serv.startChat)
self.thread.start()
def leavewindow(self):
serv.terminate() #calls function from server.py that sets flag to true
self.thread.join() #wait for thread to close
print("Thread closed")
serv.commence() #calls function from server.py that sets flag to false in case window is accessed again without exiting the program
self.master2.destroy()
server.py
import socket
import threading
import traceback
flag = threading.Event()
PORT = 5000
SERVER = socket.gethostbyname(socket.gethostname())
ADDRESS = (SERVER, PORT)
FORMAT = "utf-8"
clients, names = [], []
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(ADDRESS)
def terminate(): #set flag to true
flag.set()
def commence(): #set flag to false
flag.clear()
def startChat(): #function that listens for client connections; the aforementioned function I want to stop/break when button is pressed
print("server is working on " SERVER)
server.listen(30)
try:
while not flag.is_set(): #function runs as long as flag is not true
conn, addr = server.accept()
name = conn.recv(1024).decode(FORMAT)
clients.append(conn)
except:
print (traceback.format_exc())
Solution #2: Global Flag Variable
Same code as above but with the following changes:
mainmenu.py
def leavewindow(self):
serv.setFlag(1) #passes integer 1 to set flag to True
self.thread.join() #wait for thread to close
print("Thread closed")
serv.setFlag(0) #passes integer 0 to set flag to false in case window is accessed again without exiting the program
self.master2.destroy()
server.py
flag = False
def setFlag(a): #function receives integer to set flag value
global flag
if a == 0:
flag = False
else:
flag = True
def startChat(): #function that listens for client connections; the aforementioned function I want to stop/break when button is pressed
print("server is working on " SERVER)
server.listen(30)
try:
while True:
global flag
if flag:
break
else:
conn, addr = server.accept()
name = conn.recv(1024).decode(FORMAT)
clients.append(conn)
except:
print (traceback.format_exc())
Both of these "solutions" ends up freezing the window for an indefinite time; no errors printed in the terminal plus the line print("Thread closed")
isn't even reached.
Any advice or alternative solutions in fixing this bug is highly appreciated.
CodePudding user response:
A (if not the) major cause of an unresponsive tkinter
GUI is a callback that keeps running.
The only callback that we see is leavewindow()
.
In that function, basically only self.thread.join()
can cause this.
By default, sockets are created in blocking mode.
So for example recv
will wait until it receives data.
Which is not what you want, because your flag or event will not be tested until after data is received.
You could set a timeout on the conn
socket so it raises an exception when no data is received.
In the handler for that exception, just sleep()
in the while
-loop for some milliseconds.
def startChat(): #function that listens for client connections; the aforementioned function I want to stop/break when button is pressed
print("server is working on " SERVER)
server.listen(30)
# Not sure if the following line is needed...
server.settimeout(0.005) # time out after 5 ms.
try:
while not flag.is_set(): # works with global flag as well
conn, addr = server.accept()
conn.settimeout(0.005) # time out after 5 ms.
name = conn.recv(1024).decode(FORMAT)
clients.append(conn)
except socket.timeout:
time.sleep(0.005)
except Exception:
print (traceback.format_exc())
Or you could use the selectors
module with a nonblocking socket to only try and read data if any is available. Here you should also sleep()
in the while
-loop for some milliseconds when no data is available.