Python多线程调用ZipFile报ValueError: I/O operation on closed file问题求解
问题根本原因
- ZipFile对象自动回收关闭:你在
extract_data函数中创建的zip_file是局部变量,当你启动子线程后函数立刻返回响应,此时zip_file没有其他有效引用,Python的垃圾回收机制会触发ZipFile的析构方法,自动调用close()关闭底层IO流,子线程后续操作已经关闭的文件对象就会抛出对应错误。 - Flask请求资源自动清理:你上传的文件是Flask请求上下文中的
FileStorage对象,当接口请求结束(post方法返回响应后),Flask会自动清理请求绑定的临时资源,FileStorage对应的文件流会被直接关闭,就算ZipFile对象没有被回收,也无法读取底层的源数据。 - 补充代码笔误:你提供的
extract_function中zipfile.open(file)是拼写错误,应该是zip_file.open(file),小写z的zipfile是模块名不是你传入的对象,这个问题也会引发执行报错。
解决方案
方案1:异步逻辑内独立创建ZipFile对象(推荐)
把压缩包的原始数据先读取出来传递给子线程,在子线程内部单独创建ZipFile对象,完全不依赖主线程的局部变量和请求上下文:
from io import BytesIO def extract_function(zip_bytes): zip_file = ZipFile(BytesIO(zip_bytes)) df_list = [] try: for file in zip_file.namelist(): if file.endswith('.csv'): df_list.append(pd.read_csv(zip_file.open(file))) else: # 修正原拼写错误:zip_file而非zipfile excel_df = pd.read_excel(zip_file.open(file), None) if isinstance(excel_df, dict): df_list.extend(list(excel_df.values())) else: df_list.append(excel_df) print(len(df_list)) except Exception as e: print('Error in converting to dataframe', str(e)) def extract_data(file): # 先读取文件完整字节 zip_bytes = file.read() # 校验逻辑也用读出来的字节做 zip_file = ZipFile(BytesIO(zip_bytes)) resp = validate_function(zip_file) if resp.status_code == 200: data = Thread(target=extract_function, args=[zip_bytes]) # 设为守护线程,避免程序退出时线程挂起 data.daemon = True data.start() return resp
方案2:本地存储临时文件(适合大压缩包)
如果压缩包体积很大,全部读入内存会占用过高资源,可以先把文件写入本地临时目录,把文件路径传给子线程,子线程读取本地文件处理,处理完成后删除临时文件即可。
不推荐方案:等待子线程执行完成再返回
如果在data.start()后加data.join()等待线程执行完再返回响应,也可以避免资源提前释放,但会导致接口响应时间大幅变长,高并发下容易触发请求超时,完全失去异步执行的意义。
内容的提问来源于stack exchange,提问作者Madhavan Salai
相关产品推荐
相关产品推荐

