You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django项目重启start_auto报错RuntimeError:asyncio事件环连接后不可变更

问题分析与解决方案

错误根源

出现RuntimeError: The asyncio event loop must not change after connection的核心原因:

  1. Telethon客户端循环绑定冲突:全局的client对象第一次连接时绑定了某个异步事件循环,第二次调用start_auto时,Django为新请求分配了不同的事件循环,导致客户端无法跨循环复用。
  2. 同步代码阻塞事件循环:使用time.sleep()而非异步的asyncio.sleep(),会直接阻塞整个事件循环,破坏异步运行逻辑。
  3. 全局状态管理混乱:仅靠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

关键优化点

  1. 客户端隔离:每次启动创建新的Telethon客户端,彻底避免事件循环绑定冲突。
  2. 异步友好的IO操作:
    • 用asyncio.sleep()替代time.sleep(),不阻塞事件循环。
    • 同步sqlite操作放入线程池执行,不影响异步任务调度。
  3. 状态管理:新增running_task跟踪任务状态,防止重复启动;停止时等待任务优雅结束,确保下次启动环境干净。
  4. 代码简化:用列表推导式生成caption,移除冗余的JSON读写逻辑(如需保留则用线程池处理)。

内容的提问来源于stack exchange,提问作者gorestea

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 02:12:33