Tkinter Threading causing UI freeze - python

I've searched this site (and others) up and down but I can't seem to find the right solution.
I have a client program that connects to a server and automatically sends a message every few seconds, as well as on user command. I'm using multiple threads for this.
Enter Tkinter: Once I hit the 'Connect' button, my UI freezes, either until the connection attempt times out or until the end of time, should the client connect to the server.
I've tried calling the thread from the button's command parameter, from inside the main loop, and outside the main loop. I've tried putting the main loop in a thread and then creating a new thread for the connection from there.
Nothing seems to be working...the UI continues to hang.
class EventSim(Frame):
def __init__(self, parent):
self.queue = Queue
Frame.__init__(self, parent)
self.parent = parent
def initUI(self,IP_Address,Port_Number,Events_Directory):
#...
self.Con_Button = Button(frame7,text='Connect', command = lambda:
self.connect(IP_Text.get(),Port_Text.get(),))
def connect(self,IP,Port):
ConnectionThread = Thread(eventsim.con_thread(IP,Port))
ConnectionThread.start()
def main():
root = Tk()
root.geometry("300x310+750+300")
Sim = EventSim(root)
eventsim.readconfig()
Sim.initUI(eventsim.ipa,eventsim.portnum,eventsim.event_dir)
root.mainloop()

You pass the result of eventsim.con_thread(IP,Port) to Thread(...) function, so it will wait until the execution of eventsim.con_thread(...) completes. Try changing:
def connect(self, IP, Port):
ConnectionThread = Thread(eventsim.con_thread(IP,Port))
ConnectionThread.start()
to:
def connect(self, IP, Port):
ConnectionThread = Thread(target=lambda ip=IP, port=Port: eventsim.con_thread(ip,port))
ConnectionThread.start()

Related

RuntimeError: threads can only be started once Python Tkinter webserver

I am trying to create a webserver in python which can be started and stopped using a tkinter GUI. In tkinter I have a button which will call start() and a button that will call stop(). Initially everything works fine, the server starts when I click the button and it also stops when I click the stop button. When I try to restart the server again using the start button, I get a runtime error
RuntimeError: threads can only be started once
I believe it has something to do with the fact that I have already initialized threading in my init, and I can not figure out how to get this to work.
I have read through the threading docs multiple times, but I am struggling to understand it entirely. Any assistance would be greatly appreciated.
Thank you!
import threading
import socketserver
import http.server
import os
class WebServer(object):
def __init__(self, host, port):
self.host = host
self.port = port
self.handler = http.server.SimpleHTTPRequestHandler
self.server = socketserver.TCPServer((self.host, self.port), self.handler)
socketserver.TCPServer.allow_reuse_address = True
self.server_thread = threading.Thread(target=self.server.serve_forever, name="Server_Thread")
self.server_thread.setDaemon(True)
def start(self):
web_dir = os.path.join(os.path.dirname(__file__), 'www')
os.chdir(web_dir)
self.server_thread.start()
def stop(self):
os.chdir('..')
self.server.shutdown()
self.server.server_close()
As the python documentation states, the start method of the Thread object can only be called once.
In your case, you can create new instance of the Thread object in the start method:
def start(self):
web_dir = os.path.join(os.path.dirname(__file__), 'www')
os.chdir(web_dir)
self.server_thread = threading.Thread(target=self.server.serve_forever, name="Server_Thread")
self.server_thread.start()
In addition you also may clean the reference to the thread in stop method:
self.server_thread = None

python-xbee cannot read frame in thread

I am using one XBee S2 as coordinator (API mode), 3 XBee S2 as routers (AT mode). The routers are connected to Naze32 board (using MSP).
On the computer side, I have a GUI using wxpython to send out command to request data.
The GUI will send out command to XBee (Coordinator) to request data from the routers every second.
I am using python-xbee library to do the send and receive frame job on computer side. Once new data received, it will notify the GUI to update some labels with the new data.
Currently I am able to send and receive frames outside a thread, but once I move the send and receive functions to a thread, it will never be able to read a frame any more. As I don't want to let the serial stop the GUI or make it not responding. Another thing is if I close the thread, then start new thread with xbee again, it will not work any more.
The communication is controlled by a button on the GUI; once "Turn on" clicked, the "self._serialOn" will set to True, then create new thread; once "Turn off" clicked, "self._serialOn" will set to False and thread is stopped.
How can I fix this problem?
Thanks in advance.
class DataExchange(object):
def __init__(self):
self._observers = []
self._addressList = [['\x00\x13\xA2\x00\x40\xC1\x43\x0F', '\xFF\xFE'],[],[]]
self._serialPort = ''
self._serialOn = False
self.workerSerial = None
# serial switch
def get_serialOn(self):
return self._serialOn
def set_serialOn(self, value):
self._serialOn = value
print(self._serialOn)
if self.serialOn == True:
EVT_ID_VALUE = wx.NewId()
self.workerSerial = WorkerSerialThread(self, EVT_ID_VALUE, self.serialPort, self.addressList)
self.workerSerial.daemon = True
self.workerSerial.start()
elif self.serialOn == False:
self.workerSerial.stop()
del(self.workerSerial)
self.workerSerial = None
serialOn = property(get_serialOn, set_serialOn)
class WorkerSerialThread(threading.Thread):
def __init__(self, notify_window, id, port, addresslist):
threading.Thread.__init__(self)
self.id = id
self.serialPort = port
self.addressList = addresslist
self.counter = 0
self._notify_window = notify_window
self.abort = False
self.sch = SerialCommunication(self.serialPort, self.addressList)
try:
self.sch.PreLoadInfo()
except:
print('failed')
def run(self):
while not self.abort:
self.counter += 1
print('Serial Working on '+str(self.id))
self.sch.RegularLoadInfo()
#wx.PostEvent(self._notify_window, DataEvent(self.counter, self.id))
time.sleep(1)
def stop(self):
self.sch.board.stop()
self.abort = True
This question was finally solved with multiprocessing rather than threading of python.
In the manual of python-xbee, it mentioned that "... Make sure that updates to external state are thread-safe...". Also in the source code, threading was used.
So I guess in this case threading will cause problem.
Anyway, using multiprocessing it finally works.

Socket and threads python

I'm just experimenting with this. Whenever a user opens the program he should get 'online' and listen for connections.
Here the GUI gets loaded.
class AppUI(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.initUi()
def initUi(self):
self.parent.title("Redux")
self.pack(fill=BOTH, expand=1)
self.initMenu()
self.initAudio()
self.initMidi()
self.initBroadcast()
self.initFriendList()
But whenever I paste the code of my thread under initUi, it get's stuck on loading and my GUI doesn't show up. (keeps loading, because the thread is listening to connections)
thread2 = threading.Thread(target=Connection().getOnline("", 50007))
thread2.start()
Class Connection():
def getOnline(self, host, port):
self.s.bind((host, port))
self.s.listen(2)
print('Now online - listening to connections')
conn, addr = self.s.accept()
print("Connected to:", addr)
Why is my thread not working?
Your trouble is in this line:
thread2 = threading.Thread(target=Connection().getOnline("", 50007))
Here, you're actually calling Connection().getOnline("", 50007), which blocks. You haven't done this in the background, you've done it before your thread is started. You need to adjust your call to look like this:
thread2 = threading.Thread(target=Connection().getOnline, args = ("", 50007))

How to call a method from an already running python script

I need to check if the python script is already running then calling a method from the same running python script. But it must be on same process(pid), no new process. Is this possible?
I tried some codes but not worked.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import Tkinter as tk
from Tkinter import *
import socket
class Main():
def mainFunc(self):
self.root = tk.Tk()
self.root.title("Main Window")
self.lbl = Label(self.root, text = "First Text")
self.lbl.pack()
openStngs = Button(self.root, text = "Open Settings", command=self.settingsFunc)
openStngs.pack()
def settingsFunc(self):
stngsRoot = Toplevel()
stngsRoot.title("Settings Window")
changeTextOfLabel = Button(stngsRoot, text = "Change Main Window Text", command=self.change_text)
changeTextOfLabel.pack()
def change_text(self):
self.lbl.config(text="Text changed")
# the get_lock from http://stackoverflow.com/a/7758075/3254912
def get_lock(process_name):
lock_socket = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
try:
print lock_socket
lock_socket.bind('\0' + process_name)
print 'I got the lock'
m.mainFunc()
mainloop()
except socket.error:
print 'lock exists'
m.settingsFunc()
mainloop()
# sys.exit()
if __name__ == '__main__':
m=Main()
get_lock('myPython.py')
You either need:
A proactive check in your running process to look at the environment (for instance, the contents of a file or data coming through a socket) to know when to fire the function,
or for your running process to receive unix signals or some other IPC (possibly one of the user-defined signals) and perform a function when one is received.
Either way you can't just reach into a running process and fire a function inside that process (it MIGHT not be literally impossible if you hook the running process up to a debugger, but I wouldn't recommend it).
Tkinter necessarily has its own event loop system, so I recommend reading up on how that works and how to either run something on a timer in that event loop system, or set up a callback that responds to a signal. You could also wrap a non-event loop based system in a try/except block that will catch an exception generated by a UNIX signal, but it may not be straightforward to resume the operation of the rest of the program after that signal is caught, in that case.
Sockets are a good solution to this kind of interprocess communication problem.
One possible approach would be to set up a socket server in a thread in your original process, this can be used as an entry point for external input. A (rather stupid) example might be:
# main.py
import socket
import SocketServer # socketserver in Python 3+
import time
from Queue import Queue
from threading import Thread
# class for handling requests
class QueueHandler(SocketServer.BaseRequestHandler):
def __init__(self, request, client_address, server):
self.server = server
server.client_address = client_address
SocketServer.BaseRequestHandler.__init__(self,request, client_address, server)
# receive a block of data
# put it in a Queue instance
# send back the block of data (redundant)
def handle(self):
data = self.request.recv(4096)
self.server.recv_q.put(data)
self.request.send(data)
class TCPServer(SocketServer.TCPServer):
def __init__(self, ip, port, handler_class=QueueHandler):
SocketServer.TCPServer.__init__(self, (ip, port), handler_class, bind_and_activate=False)
self.recv_q = Queue() # a Queue for data received over the socket
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.server_bind()
self.server_activate()
def shutdown(self):
SocketServer.TCPServer.shutdown(self)
def __del__(self):
self.server_close()
# This is the equivalent of the main body of your original code
class TheClassThatLovesToAdd(object):
def __init__(self):
self.value = 1
# create and instance of the server attached to some port
self.server = TCPServer("localhost",9999)
# start it listening in a separate control thread
self.server_thread = Thread(target=self.server.serve_forever)
self.server_thread.start()
self.stop = False
def add_one_to_value(self):
self.value += 1
def run(self):
while not self.stop:
print "Value =",self.value
# if there is stuff in the queue...
while not self.server.recv_q.empty():
# read and parse the message from the queue
msg = self.server.recv_q.get()
# perform some action based on the message
if msg == "add":
self.add_one_to_value()
elif msg == "shutdown":
self.server.shutdown()
self.stop = True
time.sleep(1)
if __name__ == "__main__":
x = TheClassThatLovesToAdd()
x.run()
When you start this running, it should just loop over and over printing to the screen. Output:
Value = 1
Value = 1
Value = 1
...
However the TCPServer instance attached to the TheClassThatLovesToAdd instance now gives us a control path. The simplest looking snippet of control code would be:
# control.py
import socket
import sys
sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sock.settimeout(2)
sock.connect(('localhost',9999))
# send some command line argument through the socket
sock.send(sys.argv[1])
sock.close()
So if I run main.py in one terminal window and call python control.py add from another, the output of main.py will change:
Value = 1
Value = 1
Value = 1
Value = 2
Value = 2
...
Finally to kill it all we can run python control.py shutdown, which will gently bring main.py to a halt.
This is by no means the only solution to your problem, but it is likely to be one of the simplest.
One can try GDB, but not sure how to call a function from within [an idle thread].
Perhaps someone very versed with gdb and debugging/calling Python functions from within GDB can improve this answer.
One solution would be to use a messaging service (such as ActiveMQ or RabbitMQ). Your application subscribes to a queue/topic and whenever you want to send it a command, you write a message to it's queue. I'm not going to go into details because there are thousands of examples on-line. Queues/messaging/MQTT etc. are very simple to implement and are how most business systems (and modern control systems) communicate. Do a search for paho-mqtt.

Thread doesn't close on exiting tk window

I have an application written in Python, using Tkinter. One of the features allows a serial port to be opened, after which any messages received over the serial port are displayed in a text window. This works fine. The problem comes when I close the window, which doesn't kill the thread monitoring the serial port. It then has to be killed manually (alternatively, unplugging the USB-serial cable causes an exception which kills the process).
I assume I'm missing something simple here, but I would have thought closing the application would close all associated threads. I can't seem to find anything about this in the documentation, but I'm probably looking in the wrong place?
Code for the serial thread in case it's relevant:
class SerialThread(threading.Thread):
def __init__(self, queue, sp):
threading.Thread.__init__(self)
self.queue = queue
self.ser_handle = sp;
def run(self):
while True:
if self.ser_handle.inWaiting():
text = self.ser_handle.readline(self.ser_handle.inWaiting())
self.queue.put(text)
time.sleep(0.2)
You must have a way to ask the thread for stop, you can accomplish that using: threading.Event.
class SerialThread(threading.Thread):
def __init__(self, queue, sp):
threading.Thread.__init__(self)
self.queue = queue
self.event = threading.Event() # An event object.
self.ser_handle = sp;
def stop(self):
self.event.set()
def run(self):
while not self.event.isSet():
if self.ser_handle.inWaiting():
text = self.ser_handle.readline(self.ser_handle.inWaiting())
self.queue.put(text)
time.sleep(0.2)
Then in the on close event of your window, call your_thread.stop()

Categories

Resources