Esta inquietud se puede solucionar de diversas formas, pero te dejamos la que para nosotros es la solución más completa.
Solución:
Intentar ejecutar ambos bucles de eventos al mismo tiempo es una propuesta dudosa. Sin embargo, dado que root.mainloop simplemente llama a root.update repetidamente, uno puede simular mainloop llamando a update repetidamente como una tarea asíncrona. Aquí hay un programa de prueba que lo hace. Supongo que agregar tareas asyncio a las tareas tkinter funcionaría. Verifiqué que todavía se ejecuta con 3.7.0a2.
"""Proof of concept: integrate tkinter, asyncio and async iterator.
Terry Jan Reedy, 2016 July 25
"""
import asyncio
from random import randrange as rr
import tkinter as tk
class App(tk.Tk):
def __init__(self, loop, interval=1/120):
super().__init__()
self.loop = loop
self.protocol("WM_DELETE_WINDOW", self.close)
self.tasks = []
self.tasks.append(loop.create_task(self.rotator(1/60, 2)))
self.tasks.append(loop.create_task(self.updater(interval)))
async def rotator(self, interval, d_per_tick):
canvas = tk.Canvas(self, height=600, width=600)
canvas.pack()
deg = 0
color = 'black'
arc = canvas.create_arc(100, 100, 500, 500, style=tk.CHORD,
start=0, extent=deg, fill=color)
while await asyncio.sleep(interval, True):
deg, color = deg_color(deg, d_per_tick, color)
canvas.itemconfigure(arc, extent=deg, fill=color)
async def updater(self, interval):
while True:
self.update()
await asyncio.sleep(interval)
def close(self):
for task in self.tasks:
task.cancel()
self.loop.stop()
self.destroy()
def deg_color(deg, d_per_tick, color):
deg += d_per_tick
if 360 <= deg:
deg %= 360
color = '#%02x%02x%02x' % (rr(0, 256), rr(0, 256), rr(0, 256))
return deg, color
loop = asyncio.get_event_loop()
app = App(loop)
loop.run_forever()
loop.close()
Tanto la sobrecarga de actualización de tk como la resolución de tiempo aumentan a medida que se reduce el intervalo. Para las actualizaciones de interfaz gráfica de usuario, a diferencia de las animaciones, 20 por segundo pueden ser suficientes.
Recientemente logré ejecutar coroutines asíncronos def que contenían llamadas tkinter y espera con mainloop. El prototipo usa asyncio Tasks and Futures, pero no sé si funcionaría agregar tareas asyncio normales. Si uno quiere ejecutar tareas asyncio y tkinter juntas, creo que ejecutar tk update con un bucle asyncio es una mejor idea.
EDITAR: al menos como se usó anteriormente, la excepción sin las corrutinas asíncronas def mata a la corrutina pero se capturan y descartan en algún lugar. Los errores silenciosos son bastante desagradables.
En una ligera modificación a su código, creé el asyncio event_loop
en el subproceso principal y lo pasó como argumento al subproceso asyncio. Ahora Tkinter no se congelará mientras se recuperan las URL.
from tkinter import *
from tkinter import messagebox
import asyncio
import threading
import random
def _asyncio_thread(async_loop):
async_loop.run_until_complete(do_urls())
def do_tasks(async_loop):
""" Button-Event-Handler starting the asyncio part. """
threading.Thread(target=_asyncio_thread, args=(async_loop,)).start()
async def one_url(url):
""" One task. """
sec = random.randint(1, 8)
await asyncio.sleep(sec)
return 'url: tsec: '.format(url, sec)
async def do_urls():
""" Creating and starting 10 tasks. """
tasks = [one_url(url) for url in range(10)]
completed, pending = await asyncio.wait(tasks)
results = [task.result() for task in completed]
print('n'.join(results))
def do_freezed():
messagebox.showinfo(message='Tkinter is reacting.')
def main(async_loop):
root = Tk()
Button(master=root, text='Asyncio Tasks', command= lambda:do_tasks(async_loop)).pack()
buttonX = Button(master=root, text='Freezed???', command=do_freezed).pack()
root.mainloop()
if __name__ == '__main__':
async_loop = asyncio.get_event_loop()
main(async_loop)
Llegué un poco tarde a la fiesta, pero si no está apuntando a Windows, puede usar aiotkinter para lograr lo que desea. Modifiqué su código para mostrarle cómo usar este paquete:
from tkinter import *
from tkinter import messagebox
import asyncio
import random
import aiotkinter
def do_freezed():
""" Button-Event-Handler to see if a button on GUI works. """
messagebox.showinfo(message='Tkinter is reacting.')
def do_tasks():
task = asyncio.ensure_future(do_urls())
task.add_done_callback(tasks_done)
def tasks_done(task):
messagebox.showinfo(message='Tasks done.')
async def one_url(url):
""" One task. """
sec = random.randint(1, 15)
await asyncio.sleep(sec)
return 'url: tsec: '.format(url, sec)
async def do_urls():
""" Creating and starting 10 tasks. """
tasks = [
one_url(url)
for url in range(10)
]
completed, pending = await asyncio.wait(tasks)
results = [task.result() for task in completed]
print('n'.join(results))
if __name__ == '__main__':
asyncio.set_event_loop_policy(aiotkinter.TkinterEventLoopPolicy())
loop = asyncio.get_event_loop()
root = Tk()
buttonT = Button(master=root, text='Asyncio Tasks', command=do_tasks)
buttonT.pack()
buttonX = Button(master=root, text='Freezed???', command=do_freezed)
buttonX.pack()
loop.run_forever()
Reseñas y calificaciones del tutorial
Si conservas algún recelo o disposición de refinar nuestro crónica eres capaz de dejar una disquisición y con gusto lo observaremos.