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

Django应用中单独线程执行CSV处理函数时触发ValueError

解决Django线程中处理CSV压缩包时的ValueError问题

大概率你遇到的问题和Django上传文件的生命周期以及线程执行时机密切相关,我来拆解下可能的原因和对应的解决办法:

最核心的原因:请求结束后上传文件被自动清理

Django里request.FILES中的上传文件对象是和当前请求周期绑定的。当视图函数执行完毕、响应返回给客户端后,Django会自动关闭并删除这些临时文件。如果你的新线程是在视图返回后才开始处理文件,此时文件已经不存在,就会抛出ValueError(比如尝试读取已关闭的文件句柄,或者访问已被删除的临时文件)。

另外,你传给线程的csv_files如果是基于上传文件创建的ZipFile实例,同样会因为底层临时文件被清理而失效。

解决方案:先保存文件到本地再交给线程处理

你需要先把上传的压缩包保存到服务器的本地目录(临时目录或固定目录均可),然后让线程读取本地文件,而非直接使用请求中的文件对象。

1. 修改视图逻辑,先保存上传文件

# views.py
import os
import tempfile
import zipfile
from threading import Thread
from django.http import HttpResponse
from .update_db import upload_files

def handle_upload(request):
    if request.method == 'POST' and request.FILES.get('zip_file'):
        # 保存上传的压缩包到本地临时文件
        zip_upload = request.FILES['zip_file']
        with tempfile.NamedTemporaryFile(delete=False, suffix='.zip') as tmp_file:
            # 分块写入避免内存溢出
            for chunk in zip_upload.chunks():
                tmp_file.write(chunk)
            tmp_file_path = tmp_file.name
        
        # 启动线程,传入本地文件路径
        thread = Thread(target=process_zip_async, args=(tmp_file_path,))
        thread.start()
        
        return HttpResponse("文件处理已启动,请稍后查看数据库更新情况")
    return HttpResponse("请上传包含CSV的压缩包")

def process_zip_async(tmp_file_path):
    try:
        # 从本地路径打开ZipFile
        with zipfile.ZipFile(tmp_file_path, 'r') as csv_files:
            upload_files(csv_files)
    finally:
        # 处理完成后清理临时文件
        if os.path.exists(tmp_file_path):
            os.remove(tmp_file_path)

2. 微调upload_files函数增强稳定性

确保函数能安全处理本地ZipFile实例,同时完善数据库操作的异常处理:

# update_db.py
import sqlite3
import csv

def upload_files(csv_files):
    conn = sqlite3.connect('/path/to/db.db')
    c = conn.cursor()
    try:
        for csv_filename in csv_files.namelist():
            if csv_filename.endswith('.csv'):
                with csv_files.open(csv_filename) as csv_file:
                    reader = csv.reader(csv_file)
                    # 跳过表头(如果需要)
                    next(reader, None)
                    # 提取团队名称及其他逻辑
                    for row in reader:
                        team_name = row[0]  # 假设第一列是团队名称
                        # 写入数据库的逻辑
                        c.execute("INSERT INTO teams (name) VALUES (?)", (team_name,))
        conn.commit()
    except Exception as e:
        conn.rollback()
        # 建议添加日志记录异常,方便排查
        print(f"处理CSV时出错: {str(e)}")
        raise e
    finally:
        conn.close()

额外注意事项

  • SQLite线程安全:SQLite默认是串行化模式,每个线程单独创建连接是安全的,你当前的代码已经符合要求。
  • 线程异常捕获:线程中的异常不会自动抛到主线程,建议在process_zip_async中添加日志记录,方便后续排查问题。
  • 临时文件权限:确保Django进程拥有临时目录的读写权限,避免保存或删除文件时出现权限错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 10:06:02