Django项目重启start_auto报错RuntimeError:asyncio事件环连接后不可变更
问题分析与解决方案
错误根源
出现RuntimeError: The asyncio event loop must not change after connection的核心原因:
- Telethon客户端循环绑定冲突:全局的
client对象第一次连接时绑定了某个异步事件循环,第二次调用start_auto时,Django为新请求分配了不同的事件循环,导致客户端无法跨循环复用。 - 同步代码阻塞事件循环:使用
time.sleep()而非异步的asyncio.sleep(),会直接阻塞整个事件循环,破坏异步运行逻辑。 - 全局状态管理混乱:仅靠
stop_event无法正确跟踪任务状态,重复启动时会导致多个任务或循环冲突。
代码修复方案
1. 重构tgbot.py
import asyncio import json import sqlite3 from telethon import TelegramClient from telethon.errors import PermissionError # 建议将API信息移至环境变量或配置文件 API_ID = 123456 API_HASH = "your_api_hash_here" async def send_to_telegram(stop_event): # 每次启动创建独立客户端,避免跨事件循环绑定 async with TelegramClient("telegram_session", API_ID, API_HASH) as client: await client.connect() # 用线程池执行同步sqlite操作,避免阻塞事件循环 def fetch_cars_from_db(): conn = sqlite3.connect("db.sqlite3") cursor = conn.execute("SELECT * FROM carparcapp_car") cars = [] for row in cursor: cars.append({ "Марка": row[1], "Тип": row[2], "Кабина": row[3], "Формула": row[4], "Двигатель": row[5], "Мощность": row[6], "КПП": row[7], "Мосты": row[8], "Евро-класс": row[9], "Фото": f"media/{row[10]}" }) conn.close() return cars loop = asyncio.get_running_loop() cars = await loop.run_in_executor(None, fetch_cars_from_db) # 可选:保存JSON文件(用线程池避免阻塞) def save_cars_to_json(): with open("cars.json", "w", encoding="utf-8") as f: json.dump(cars, f, indent=4, ensure_ascii=False) await loop.run_in_executor(None, save_cars_to_json) # 读取频道列表 with open("channels.json", "r", encoding="utf-8") as f: channels = json.load(f) index = 0 while not stop_event.is_set() and index < len(cars): for channel in channels: if stop_event.is_set(): break car = cars[index] # 简化caption生成 caption = "\n".join([f"{k}: {v}" for k, v in car.items() if k != "Фото"]) try: photo_path = car.get("Фото") with open(photo_path, "rb") as photo_file: await client.send_file(channel, photo_file, caption=caption) await asyncio.sleep(10) # 异步sleep,不阻塞循环 except PermissionError: await client.send_message(channel, caption) await asyncio.sleep(5) index += 1 if index >= len(cars): break
2. 重构views.py
import asyncio from django.http import HttpResponseRedirect from django.views.decorators.http import require_http_methods from .tgbot import send_to_telegram # 全局变量跟踪任务状态 running_task = None stop_event = None @require_http_methods(["POST"]) # 用POST避免GET请求误触发 async def start_auto(request): global running_task, stop_event # 避免重复启动任务 if running_task and not running_task.done(): return HttpResponseRedirect("/") stop_event = asyncio.Event() running_task = asyncio.create_task(send_to_telegram(stop_event)) return HttpResponseRedirect("/") @require_http_methods(["POST"]) def stop_auto(request): global running_task, stop_event if stop_event: stop_event.set() # 等待任务优雅结束后重置状态 if running_task and not running_task.done(): asyncio.create_task(_wait_for_task_cleanup()) print("Программа остановлена!") return HttpResponseRedirect("/") async def _wait_for_task_cleanup(): global running_task await running_task running_task = None
关键优化点
- 客户端隔离:每次启动创建新的Telethon客户端,彻底避免事件循环绑定冲突。
- 异步友好的IO操作:
- 用
asyncio.sleep()替代time.sleep(),不阻塞事件循环。 - 同步sqlite操作放入线程池执行,不影响异步任务调度。
- 用
- 状态管理:新增
running_task跟踪任务状态,防止重复启动;停止时等待任务优雅结束,确保下次启动环境干净。 - 代码简化:用列表推导式生成caption,移除冗余的JSON读写逻辑(如需保留则用线程池处理)。
内容的提问来源于stack exchange,提问作者gorestea
相关产品推荐
相关产品推荐

