Skip to content

Instantly share code, notes, and snippets.

@jsbueno
Last active September 7, 2021 03:10
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jsbueno/0297113b60b783d5bf2db8360db06eaf to your computer and use it in GitHub Desktop.
Save jsbueno/0297113b60b783d5bf2db8360db06eaf to your computer and use it in GitHub Desktop.
asyncio tkinter
"""
Sample file to enable tkinter to run with co-routine and asyncio code
with no need for extra threads or blocking the UI
(this will likely be upgraded to a full package at some point in time)
"""
import asyncio, sys, tkinter
from functools import wraps
import inspect
_tkasync_tasks = set()
def tkasync(func):
"""Decorator to enable asyncio callbacks for tkinter
"""
if not inspect.iscoroutinefunction(func):
return func # nothing to be done
@wraps(func)
def wrapper(*args, **kwargs):
coro = func(*args, **kwargs)
try:
loop = asyncio.get_running_loop()
except RuntimeError:
# no running event loop: we are running in sync context:
# just create a loop, await for the wrapped function and be done with it
asyncio.run(coro)
return
task = loop.create_task(coro)
_tkasync_tasks.add(task)
return wrapper
async def main_async(root):
"""Tkinter async driver, meant to replace tkinter.mainloop
"""
global _tkasync_tasks
while True:
try:
root.update()
except Exception as error:
# FIXME: suppress error message when tkinter app is destroyed.
print(error, file=sys.stderr)
raise
if _tkasync_tasks:
complete, pending = await asyncio.wait(_tkasync_tasks, timeout=0.005)
del complete
_tkasync_tasks = pending
else:
await asyncio.sleep(0.005)
############################################################
# Example code just to show something running in parallel
index = 0
interval=250
area = button = root = None
def build():
global root, area, button
root = tkinter.Tk()
button = b = tkinter.Button(root, text="ok", command=click)
area =l = tkinter.Label(root, text=" " * 80, background="yellow")
b.pack()
area.pack()
root.after(interval, update_color)
return root
def update_color():
global index
colors = "yellow", "red", "blue", "black", "green"
index += 1
index %= len(colors)
area["background"] = colors[index]
root.after(interval, update_color)
#####################################################
# Example coroutine used as a tkinter callback:
@tkasync
async def click():
state = button["state"]
button["state"] = "disabled"
print("click enter")
await asyncio.sleep(2)
print("click exit")
button["state"] = state if state != "disabled" else "normal"
##########################################
# sample main function
def main():
root = build()
# Call this instead of tkinter.mainloop:
loop = asyncio.run(main_async(root))
if __name__ == "__main__":
main()
@plainspooky
Copy link

At line 64 there is an unpack error on area, button, root = None, shouldn't it be something like area = button = root = None?

@jsbueno
Copy link
Author

jsbueno commented Sep 7, 2021

Yes. Fixed now, thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment