В коде нет работы с потоками, ты даже не импортируешь threading.
Нужно понимать три вещи:
1. Рабочий цикл бота займёт весь поток, равно как и рабочий цикл tkinter. Совместить их практически невозможно (технически можно, но это очень нетривиально).
2. Элементы GUI должны создаваться и использоваться строго в одном и том же потоке.
3. Нельзя делать await вызовы из одного потока в другой.
Поэтому я бы посоветовал такое грубое, но простое решение:
В одном потоке потоке создавай event loop (сам! это важно!) и запускай бота. В другом потоке создавай и запускай GUI.
Какой поток должен быть главным - скорее всего не принципиально. Попробуй оба варианта.
Используй пару очередей (
queue.Queue) для синхронизации между потоками.
Одна очередь будет периодически проверяться в потоке GUI с помощью root.after(). Почитай документацию на этот метод, но если коротко - он позволяет запланировать вызов функции в потоке GUI через время. Эта очередь будет содержать сведения о том, что нужно обновить в GUI.
Формат и смысл сведений определи сам. Это может быть что-то высокоуровневое в духе "есть сообщение от пользователя такого-то с таким-то текстом", и пусть поток gui сам разбирается, что с этим делать. Это может быть и что-то более низкоуровневое, типа "задай такому-то свойству у такого-то элемента управления такое-то значение". Я бы посоветовал первый вариант - он позволит разделить логику программы чётко на две части.
Код проверки будет примерно вида
def check_gui_queue():
try:
while True:
command = gui_queue.get_nowait() # проверяем, есть ли команда для GUI
gui_queue.task_done() # на каждый успешный вызов get() - один вызов task_done()
pass # как-то обрабатываем команду
except queue.Empty: # команды нет
root.after(100, check_gui_queue) # даём GUI поработать спокойно 100 мс
Разумеется, если ты завернёшь GUI в класс (что имеет смысл) код немного изменится. Но суть останется прежней.
Другая очередь будет содержать команды для бота. Периодически проверяй её в потоке asyncio с помощью простого кода вида
async def check_bot_queue():
while True:
try:
command = bot_queue.get_nowait() # проверяем, есть ли новая команда для бота
except queue.Empty:
await asyncio.sleep(0.1) # нет - даём другим корутинам поработать 100 мс
else:
bot_queue.task_done() # на каждый успешный вызов get() - один вызов task_done()
pass # есть - как-то её обрабатываем
Эту корутину запустишь через create_task(), и она позволит коду GUI организовывать вызовы в коде бота.
Разумеется, придётся подумать, как аккуратно сделать, чтобы эти две функции не разрослись в дикую простыню из if-elif-else. Но общий принцип примерно такой.
Время ожидания команды можно увеличить - это замедлит время реакции пре передаче данных между потоками, но уменьшит холостую нагрузку на систему.