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

tkinter读取实时更新JSON文件时频繁触发JSONDecodeError

解决跨程序JSON文件读写冲突导致的空JSON错误

问题背景

  • 用tkinter实现覆盖层,通过after()定时读取被其他程序持续更新的JSON文件
  • 单独运行时功能正常,手动修改JSON后覆盖层能同步更新;但同时运行两个程序时,会随机触发空JSON解析错误,触发时间无规律(5分钟到45分钟不等)

已尝试但无效的方案

  • 先检查JSON格式有效性,再读取文件
  • 判断文件大小大于0后再读取
  • 分奇偶秒错开两个程序的读写时机

根本原因

两个程序直接对同一文件进行读写时,写操作未完成(比如正在清空文件、写入半完整的JSON)的瞬间,读操作触发,导致读到空内容或不完整的JSON结构,进而引发解析错误。之前的方案要么没解决读写的原子性问题,要么误用time.sleep阻塞tkinter主循环,反而加剧异常概率。

有效解决方案

方案1:跨进程文件锁同步

利用平台原生的文件锁机制,确保同一时间只有一个进程能访问JSON文件,避免读写交叉。

示例代码(兼容Windows/Linux/macOS):

import json
import os
import sys
import tkinter as tk

# 跨平台文件锁实现
class FileLock:
    def __init__(self, file_path):
        self.file_path = file_path
        self.lock_file = f"{file_path}.lock"
        self.fd = None

    def acquire(self):
        if sys.platform == 'win32':
            import msvcrt
            self.fd = open(self.lock_file, 'w')
            msvcrt.locking(self.fd.fileno(), msvcrt.LK_NBLCK, 1)
        else:
            import fcntl
            self.fd = open(self.lock_file, 'w')
            fcntl.flock(self.fd, fcntl.LOCK_EX | fcntl.LOCK_NB)

    def release(self):
        if self.fd:
            if sys.platform == 'win32':
                import msvcrt
                msvcrt.locking(self.fd.fileno(), msvcrt.LK_UNLCK, 1)
            else:
                import fcntl
                fcntl.flock(self.fd, fcntl.LOCK_UN)
            self.fd.close()
            try:
                os.remove(self.lock_file)
            except:
                pass

def read_json_safely(file_path):
    lock = FileLock(file_path)
    try:
        lock.acquire()
        with open(file_path, 'r', encoding='utf-8') as f:
            try:
                return json.load(f)
            except json.JSONDecodeError:
                # 读到不完整JSON,返回上次有效数据
                return None
    except (BlockingIOError, PermissionError):
        # 锁被占用,跳过本次读取
        return None
    finally:
        lock.release()

# tkinter 界面更新逻辑
class OverlayWindow(tk.Tk):
    def __init__(self):
        super().__init__()
        self.label = tk.Label(self, text="等待数据...", font=("Arial", 16))
        self.label.pack(padx=20, pady=20)
        self.last_valid_data = {}
        self.update_data()

    def update_data(self):
        data = read_json_safely("my_json")
        if data is not None:
            self.last_valid_data = data
            self.label.config(text=f"当前值:{data.get('target_key', '无')}")
        # 用after实现定时,绝不使用time.sleep阻塞主循环
        self.after(1000, self.update_data)

if __name__ == "__main__":
    app = OverlayWindow()
    app.mainloop()

方案2:写程序改用临时文件原子替换(推荐)

修改更新JSON的程序,先将内容写入临时文件,完成后再替换目标JSON文件——文件替换操作是原子性的,读程序永远只会读到完整的JSON内容。

写程序核心代码:

import json
import os

def write_json_safely(file_path, data):
    temp_path = f"{file_path}.tmp"
    # 先写入临时文件
    with open(temp_path, 'w', encoding='utf-8') as f:
        json.dump(data, f, ensure_ascii=False)
    # 原子替换目标文件
    os.replace(temp_path, file_path)

此方案下读程序无需额外处理,直接正常读取即可,彻底避免不完整JSON的问题。

方案3:严格遵循tkinter异步规则

绝对不要在tkinter主线程中使用time.sleep,所有定时任务必须通过after()实现,否则会阻塞界面渲染和事件处理,引发不可预料的异常。

关键注意事项

  • tkinter主循环不能被阻塞,after()是实现定时任务的唯一正确方式
  • 跨进程文件操作必须保证原子性,锁或临时文件二选其一
  • 读取时捕获JSONDecodeError,用上次有效数据兜底,避免界面报错

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 19:10:24