A friend of mine wrote a program that used curio and tkinter, and it ran tkinter’s mainloop in the main thread and
curio.run in another thread. I needed to run tkinter with curio at some point too, and I figured out a way to run tkinter’s mainloop in curio. It works like this:
import curio import functools import tkinter as tk try: from _tkinter import DONT_WAIT except ImportError: # _tkinter.DONT_WAIT is there on cpython and pypy, but providing a # fallback doesn't hurt DONT_WAIT = 2 async def tk_mainloop(root): try: dooneevent = root.tk.dooneevent except AttributeError: # probably running in pypy dooneevent = _tkinter.dooneevent while True: dooneevent(DONT_WAIT) await curio.sleep(0) # stop if the root window is destroyed try: root.winfo_exists() except tk.TclError: break # simple example async def counter(): # this is here just to prove that tk_mainloop() doesn't block i = 0 while True: print("counter:", i) i += 1 await curio.sleep(1) async def main(): root = tk.Tk() button = tk.Button(root, text="Click me", command=functools.partial(print, "you clicked me")) button.pack() countertask = await curio.spawn(counter()) await tk_mainloop(root) await countertask.cancel() curio.run(main)
dooneevent() calls Tcl_DoOneEvent(3tcl), and it runs Tk’s mainloop by one iteration.
My code works, but there are some problems with it. Most of these are probably not too hard to fix and there might already be a workaround that I haven’t found yet.
- The code is heavy on the CPU because curio’s main loop runs as fast as possible, and nothing slows it down. I have no idea how to fix this. Urwid’s mainloops seem to just call
curio.run(curio.spawn(stuff))to spawn an async function from a tkinter callback doesn’t work.
I think it would be nice to see something like a
curio.tkinter submodule for writing tkinter code with curio. Using
curio.sleep(0) feels like a hack, and it would be really nice to just do
button['command'] = some_async_func and have it run correctly in curio’s mainloop.