Ошибка с Tkinter и асинхроном

Пишу прогу смеха ради:

from tkinter import *
from tkinter import ttk
import asyncio

root = Tk()
root.title("Управление кораблём")
root.geometry("200x100")

PlanetSelection = ttk.Combobox(values=["---------------------------","Sol-1(Меркурий)", "Sol-2(Венера)", "Sol-3 (Земля)", "Sol-4(Марс)", "Sol-5(Юпитер)", "Sol-6(Сатурн)", "Sol-9(Плутон)", "---------------------------"])

console = ttk.Label(text="")

async def startCom():
    console.config(text="5")
    await asyncio.sleep(1)
    console.config(text="4")
    await asyncio.sleep(1)
    console.config(text="3")
    await asyncio.sleep(1)
    console.config(text="2")
    await asyncio.sleep(1)
    console.config(text="1")
    await asyncio.sleep(0.2)
    console.config(text="Запуск!")

start = Button(text="Запуск!", command=startCom)

PlanetSelection.pack()
start.pack()
console.pack()

root.mainloop()

Когда я нажимаю на кнопку "Запуск!" появляется эта ошибка:

C:\Python311\Lib\tkinter\__init__.py:1485: RuntimeWarning: coroutine 'startCom' was never awaited
  self.tk.mainloop(n)
RuntimeWarning: Enable tracemalloc to get the object allocation traceback

Скорее всего проблема в том что функция асинхронная.


Ответы (1 шт):

Автор решения: insolor

tkinter из коробки не умеет работать с асинхронностью. Для использования асинхронности в tkinter можно использовать, например, мою библиотеку async-tkinter-loop
(pip install async-tkinter-loop):

from tkinter import *
from tkinter import ttk
import asyncio

from async_tkinter_loop import async_handler, async_mainloop

root = Tk()
root.title("Управление кораблём")
root.geometry("200x100")

PlanetSelection = ttk.Combobox(values=[
   "---------------------------",
   "Sol-1(Меркурий)",
   "Sol-2(Венера)",
   "Sol-3 (Земля)",
   "Sol-4(Марс)",
   "Sol-5(Юпитер)",
   "Sol-6(Сатурн)",
   "Sol-9(Плутон)",
   "---------------------------",
])

console = ttk.Label(text="")

@async_handler  # <--- Добавить декоратор
async def startCom():
    console.config(text="5")
    await asyncio.sleep(1)
    console.config(text="4")
    await asyncio.sleep(1)
    console.config(text="3")
    await asyncio.sleep(1)
    console.config(text="2")
    await asyncio.sleep(1)
    console.config(text="1")
    await asyncio.sleep(0.2)
    console.config(text="Запуск!")

start = Button(text="Запуск!", command=startCom)

PlanetSelection.pack()
start.pack()
console.pack()

async_mainloop(root)  # <--- запуск асинхронного цикла обработки событий

В целом, обычно в tkinter для таймера используют метод .after:

def startCom(n=5):
    if n > 0:
        console.config(text=n)
        t = 1000 if n > 1 else 200
        root.after(t, startCom, n-1)
    else:
        console.config(text="Запуск!")


start = Button(text="Запуск!", command=startCom)

Тут с помощью root.after планируем следующий запуск функции через указанное количество миллисекунд. Третьим параметром передаем аргумент, который при следующем запуске будет передан в функцию, в данном случае — следующее значение счетчика. Когда счетчик становится равным 0, просто выводим сообщение без планирования повторного запуска.

→ Ссылка