How to Update the Gui from Another Thread

How do I update the GUI from another thread?

For .NET 2.0, here's a nice bit of code I wrote that does exactly what you want, and works for any property on a Control:

private delegate void SetControlPropertyThreadSafeDelegate(
Control control,
string propertyName,
object propertyValue);

public static void SetControlPropertyThreadSafe(
Control control,
string propertyName,
object propertyValue)
{
if (control.InvokeRequired)
{
control.Invoke(new SetControlPropertyThreadSafeDelegate
(SetControlPropertyThreadSafe),
new object[] { control, propertyName, propertyValue });
}
else
{
control.GetType().InvokeMember(
propertyName,
BindingFlags.SetProperty,
null,
control,
new object[] { propertyValue });
}
}

Call it like this:

// thread-safe equivalent of
// myLabel.Text = status;
SetControlPropertyThreadSafe(myLabel, "Text", status);

If you're using .NET 3.0 or above, you could rewrite the above method as an extension method of the Control class, which would then simplify the call to:

myLabel.SetPropertyThreadSafe("Text", status);

UPDATE 05/10/2010:

For .NET 3.0 you should use this code:

private delegate void SetPropertyThreadSafeDelegate<TResult>(
Control @this,
Expression<Func<TResult>> property,
TResult value);

public static void SetPropertyThreadSafe<TResult>(
this Control @this,
Expression<Func<TResult>> property,
TResult value)
{
var propertyInfo = (property.Body as MemberExpression).Member
as PropertyInfo;

if (propertyInfo == null ||
!@this.GetType().IsSubclassOf(propertyInfo.ReflectedType) ||
@this.GetType().GetProperty(
propertyInfo.Name,
propertyInfo.PropertyType) == null)
{
throw new ArgumentException("The lambda expression 'property' must reference a valid property on this Control.");
}

if (@this.InvokeRequired)
{
@this.Invoke(new SetPropertyThreadSafeDelegate<TResult>
(SetPropertyThreadSafe),
new object[] { @this, property, value });
}
else
{
@this.GetType().InvokeMember(
propertyInfo.Name,
BindingFlags.SetProperty,
null,
@this,
new object[] { value });
}
}

which uses LINQ and lambda expressions to allow much cleaner, simpler and safer syntax:

myLabel.SetPropertyThreadSafe(() => myLabel.Text, status); // status has to be a string or this will fail to compile

Not only is the property name now checked at compile time, the property's type is as well, so it's impossible to (for example) assign a string value to a boolean property, and hence cause a runtime exception.

Unfortunately this doesn't stop anyone from doing stupid things such as passing in another Control's property and value, so the following will happily compile:

myLabel.SetPropertyThreadSafe(() => aForm.ShowIcon, false);

Hence I added the runtime checks to ensure that the passed-in property does actually belong to the Control that the method's being called on. Not perfect, but still a lot better than the .NET 2.0 version.

If anyone has any further suggestions on how to improve this code for compile-time safety, please comment!

How do I update the GUI from another thread? using python

Of course you can use Threading to run several processes simultaneously.

You have to create a class like this :

from threading import Thread

class Work(Thread):

def __init__(self):
Thread.__init__(self)
self.lock = threading.Lock()

def run(self): # This function launch the thread
(your code)

if you want run several thread at the same time :

def foo():
i = 0
list = []
while i < 10:
list.append(Work())
list[i].start() # Start call run() method of the class above.
i += 1

Be careful if you want to use the same variable in several threads. You must lock this variable so that they do not all reach this variable at the same time. Like this :

lock = threading.Lock()
lock.acquire()
try:
yourVariable += 1 # When you call lock.acquire() without arguments, block all variables until the lock is unlocked (lock.release()).
finally:
lock.release()

From the main thread, you can call join() on the queue to wait until all pending tasks have been completed.

This approach has the benefit that you are not creating and destroying threads, which is expensive. The worker threads will run continuously, but will be asleep when no tasks are in the queue, using zero CPU time.

I hope it will help you.

Update data in a Tkinter-GUI with data from a second Thread

This solution is based on comments from other person. It use queue.Queue to share data between the two threads. The Tkinter GUI/Thread use a 1-second-timer to check if new data is in the Queue and use it to refresh its Labels.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# based on <https://stackoverflow.com/a/47920128/4865723>

from tkinter import *
import asyncio
import threading
import random
import queue


class AsyncioThread(threading.Thread):
def __init__(self, the_queue, max_data):
self.asyncio_loop = asyncio.get_event_loop()
self.the_queue = the_queue
self.max_data = max_data
threading.Thread.__init__(self)

def run(self):
self.asyncio_loop.run_until_complete(self.do_data())

async def do_data(self):
""" Creating and starting 'maxData' asyncio-tasks. """
tasks = [
self.create_dummy_data(key)
for key in range(self.max_data)
]
await asyncio.wait(tasks)

async def create_dummy_data(self, key):
""" Create data and store it in the queue. """
sec = random.randint(1, 10)
data = '{}:{}'.format(key, random.random())
await asyncio.sleep(sec)

self.the_queue.put((key, data))


class TheWindow:
def __init__(self, max_data):
# thread-safe data storage
self.the_queue = queue.Queue()

# the GUI main object
self.root = Tk()

# create the data variable
self.data = []
for key in range(max_data):
self.data.append(StringVar())
self.data[key].set('<default>')

# Button to start the asyncio tasks
Button(master=self.root,
text='Start Asyncio Tasks',
command=lambda: self.do_asyncio()).pack()
# Frames to display data from the asyncio tasks
for key in range(max_data):
Label(master=self.root, textvariable=self.data[key]).pack()
# Button to check if the GUI is freezed
Button(master=self.root,
text='Freezed???',
command=self.do_freezed).pack()

def refresh_data(self):
"""
"""
# do nothing if the aysyncio thread is dead
# and no more data in the queue
if not self.thread.is_alive() and self.the_queue.empty():
return

# refresh the GUI with new data from the queue
while not self.the_queue.empty():
key, data = self.the_queue.get()
self.data[key].set(data)

print('RefreshData...')

# timer to refresh the gui with data from the asyncio thread
self.root.after(1000, self.refresh_data) # called only once!

def do_freezed(self):
""" Button-Event-Handler to see if a button on GUI works.
The GOAL of this example is to make this button clickable
while the other thread/asyncio-tasks are working. """
print('Tkinter is reacting. Thread-ID: {}'
.format(threading.get_ident()))

def do_asyncio(self):
"""
Button-Event-Handler starting the asyncio part in a separate
thread.
"""
# create Thread object
self.thread = AsyncioThread(self.the_queue, len(self.data))

# timer to refresh the gui with data from the asyncio thread
self.root.after(1000, self.refresh_data) # called only once!

# start the thread
self.thread.start()


if __name__ == '__main__':
window = TheWindow(10)
window.root.mainloop()

This example is based on https://stackoverflow.com/a/47920128/4865723.
Not sure if this is an elegant solution. Please feel free to edit this. It is my goal to make my question and the answer reusable by others.

Updating GUI from different thread

Given you only need to run one function i would recommend QtConcurrent. Extract form the docs:

Running a Function in a Separate Thread

To run a function in another thread, use QtConcurrent::run():

extern void aFunction();
QFuture<void> future = QtConcurrent::run(aFunction);

This will run aFunction in a separate thread obtained from the default QThreadPool. You can use the QFuture and QFutureWatcher classes to monitor the status of the function.

Update Tkinter UI from another thread

You can use queue.SimpleQueue to pass data from Server to MainPage so that MainPage can insert the data into the Listbox:

main.py:

from queue import SimpleQueue
...
class App(tk.Tk):
def __init__(self, queue, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.queue = queue
...

if __name__ == "__main__":
queue = SimpleQueue()
server = Server(queue) # pass queue to Server
app = App(queue) # pass queue to App
...

server.py:

class Server:
...
def __init__(self, queue=None):
...
self.queue = queue

def __accept_incoming_connection(self):
while self.__is_running:
client, client_address = self.__server.accept()
# I want to insert text into listbox of the MainPage (msg_list) here
if self.queue:
self.queue.put(client_address) # or whatever information you want
...

main_page.py:

class MainPage(tk.Frame):
def __init__(self, parent, controller):
....
# call a monitor task periodically
self.monitor_queue()

def monitor_queue(self):
if not self.controller.queue.empty():
caddr = self.controller.queue.get()
self.msg_list.insert(tk.END, str(caddr))
self.msg_list.see(tk.END)
self.after(100, self.monitor_queue)

Below is an example using similar structure as your code:

import tkinter as tk
from queue import SimpleQueue
from socket import socket, AF_INET, SOCK_STREAM
import threading

HOST = ""
PORT = 3000
ADDRESS = (HOST, PORT)

class Server(threading.Thread):
__instance = None

@staticmethod
def get_instance():
if Server.__instance is None:
Server()
return Server.__instance

def __init__(self, queue=None):
if Server.__instance:
raise Exception("Server is singleton class")
super().__init__()
Server.__instance = self
self._server = socket(AF_INET, SOCK_STREAM)
self._server.bind(ADDRESS)
self._server.listen()

self.queue = queue

def set_queue(self, queue):
self.queue = queue

def run(self):
print("Server started")
while True:
try:
client, client_address = self._server.accept()
if self.queue:
self.queue.put(client_address)
except Exception as e:
print(e)
break
print("Server terminated")

def terminate(self):
self._server.close() # self._server.accept() will raise exception and break the while loop

class SetupPage(tk.Frame):
def __init__(self, parent, controller):
super().__init__(parent)
self.controller = controller
tk.Label(self, text="Setup Page").pack(padx=100, pady=50)
tk.Button(self, text="Goto Main page", command=lambda: self.controller.show_frame("MainPage")).pack()

class MainPage(tk.Frame):
def __init__(self, parent, controller):
super().__init__(parent)
self.controller = controller

self.scrollbar = tk.Scrollbar(self)
self.msg_list = tk.Listbox(self, width=50, height=15, yscrollcommand=self.scrollbar.set)

self.scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.msg_list.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

self.monitor_queue()

def monitor_queue(self):
if not self.controller.queue.empty():
caddr = self.controller.queue.get()
self.msg_list.insert(tk.END, str(caddr))
self.msg_list.see(tk.END)
self.after(100, self.monitor_queue)

class App(tk.Tk):
def __init__(self, queue):
super().__init__()
self.queue = queue

container = tk.Frame(self)
container.pack(side=tk.TOP, fill=tk.BOTH, expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)

self.frames = {}
for F in (SetupPage, MainPage):
frame = F(parent=container, controller=self)
frame.grid(row=0, column=0, sticky="nsew")
self.frames[F.__name__] = frame

self.show_frame("SetupPage")

def show_frame(self, page_name):
frame = self.frames[page_name]
frame.tkraise()

if __name__ == "__main__":
queue = SimpleQueue()
server = Server(queue)
server.start()
app = App(queue)
app.title("Server")
app.geometry("600x450")
app.mainloop()
server.terminate()


Related Topics



Leave a reply



Submit