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

删除.json文件时触发System.IO.IOException问题求助

批量删除文件夹时随机出现JSON文件被占用无法删除的问题

我正在开发一套批量删除文件夹的系统,每个目标文件夹里都包含一个.json文件及其他文件/子目录。但每次执行删除操作时,总会有随机的.json文件因被其他进程占用而无法删除,触发System.IO.IOException。

我已尝试以下排查方案,但均无效:

  • 将目录的FileAttributes设置为Normal(曾解决其他非.json目录的同类问题)
  • 将访问目录的线程设为后台线程,确保应用停止后无残留线程
  • 删除前关闭文件资源管理器
  • 设置保存文件的流为ReadWrite权限
  • 释放写入.json文件的StreamWriter而非关闭它

目前仅删除.json文件时会出现该异常,求问还有哪些遗漏的排查点?


相关代码

删除文件的函数

public static void DeleteBrains()
{
    Console.WriteLine("Resetting brains...");

    Dictionary<int, Thread> threads = new Dictionary<int, Thread>();
    for (int i = 0; i < 100; i++)
    {
        DeleteDirProtector(i);
    }
    StartThreads(threads, false);
    
    void DeleteDirProtector(int i)
    {
        Thread thread = new Thread(() => DeleteDir(i));
        threads.Add(i, thread);
    }
    void DeleteDir(int i)
    {
        string CurrentDir = $@"{DebugFolderPath}\Brain {i}";
        if (Directory.Exists(CurrentDir))
        {
            Console.WriteLine($"Deleting Brain {i}...");
            File.SetAttributes(CurrentDir, FileAttributes.Normal);
            string[] directories = Directory.GetDirectories(CurrentDir);
            string[] files = Directory.GetFiles(CurrentDir);
            for (int j = 0; j < directories.Length; j++)
            {
                File.SetAttributes(directories[j], FileAttributes.Normal);
            }
            for (int j = 0; j < files.Length; j++)
            {
                File.SetAttributes(files[j], FileAttributes.Normal);
            }
            Directory.Delete(CurrentDir, true);
        }
    }
}

保存JSON文件的函数

public void SaveBrain(bool isUpdate)
{
    BrainSaveString = JsonConvert.SerializeObject(this, Formatting.Indented);
    string path = "";
    if (isUpdate)
    {
        path = BrainSavePath2;
    }
    else if (!isUpdate)
    {
        path = BrainSavePath;
    }
    FileStream stream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite);
    using (StreamWriter sw = new StreamWriter(stream))
    {
        sw.Write(BrainSaveString);
        sw.Dispose();
    }
}

StartThreads方法

public static void StartThreads(Dictionary<int, Thread> threads, bool isBackground)
{
    int threadCount = threads.Count;

    for (int i = 0; i < threadCount; i++)
    {
        try
        {
            if (threads[i].ThreadState == ThreadState.Unstarted)
            {
                while (!StartThread(threads[i], false, isBackground)){}
            }
        }
        catch (KeyNotFoundException) { }
    }
    while (threads.Count > 0) 
    {
        for(int i = 0; i < threadCount; i++)
        {
            try
            {
                if (threads[i].ThreadState == ThreadState.Stopped)
                {
                    threads.Remove(i);
                }
            }
            catch (KeyNotFoundException) { }
        }
    }
}

StartThread方法

public static bool StartThread(Thread thread, bool ShouldRunAnyway, bool isBackground)
{
    bool shouldStartThread = false;
    if (NumberOfThreads_ < NumberOfProcessors * 3)
    {
        shouldStartThread = true;
    }
    else
    {
        if (ShouldRunAnyway)
        {
            shouldStartThread = true;
        }
    }
    if (shouldStartThread)
    {
        thread.IsBackground = isBackground;
        thread.Start();
        lock (AllThreadsBeingUsed)
        {
            AllThreadsBeingUsed.Add(thread);
        }
    }
    
    return shouldStartThread;
}

遗漏的排查点及修复建议

  1. 修复SaveBrain方法的资源释放逻辑
    你在SaveBrain中手动调用sw.Dispose(),但using块本身会自动释放StreamWriter及其持有的FileStream,手动调用可能导致资源释放不彻底。同时FileStream未被包裹在using中,存在流未完全关闭的风险。修改后的代码如下:

    public void SaveBrain(bool isUpdate)
    {
        BrainSaveString = JsonConvert.SerializeObject(this, Formatting.Indented);
        string path = isUpdate ? BrainSavePath2 : BrainSavePath;
        using (FileStream stream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite))
        using (StreamWriter sw = new StreamWriter(stream))
        {
            sw.Write(BrainSaveString);
        }
    }
    
  2. 排查内部线程的并发冲突

    • 确认执行DeleteBrains时,所有SaveBrain调用已完全结束,可通过全局锁或信号量确保保存操作全部完成后再启动删除线程。
    • 用Windows的Process Explorer工具搜索目标JSON文件路径,查看是否有本进程内的其他线程持有文件句柄。
  3. 强制回收未释放的资源
    CLR的垃圾回收可能延迟释放未被引用的文件句柄,可在删除前强制触发一次垃圾回收(仅排查阶段试用):

    GC.Collect();
    GC.WaitForPendingFinalizers();
    

    将这段代码放在Directory.Delete之前测试。

  4. 检查第三方工具的文件锁定
    杀毒软件、备份工具、Windows搜索索引服务等都可能临时锁定JSON文件,可临时关闭这些服务测试,或在删除前暂停索引服务。

  5. 添加删除重试机制
    随机出现的文件占用大概率是临时锁定,给删除操作加重试逻辑:

    void DeleteDir(int i)
    {
        string CurrentDir = $@"{DebugFolderPath}\Brain {i}";
        if (!Directory.Exists(CurrentDir)) return;
    
        Console.WriteLine($"Deleting Brain {i}...");
        const int maxRetries = 3;
        int retryCount = 0;
        bool deleted = false;
    
        while (!deleted && retryCount < maxRetries)
        {
            try
            {
                File.SetAttributes(CurrentDir, FileAttributes.Normal);
                foreach (var dir in Directory.GetDirectories(CurrentDir))
                    File.SetAttributes(dir, FileAttributes.Normal);
                foreach (var file in Directory.GetFiles(CurrentDir))
                    File.SetAttributes(file, FileAttributes.Normal);
                Directory.Delete(CurrentDir, true);
                deleted = true;
            }
            catch (IOException)
            {
                retryCount++;
                Thread.Sleep(500);
                Console.WriteLine($"Retrying delete Brain {i} (attempt {retryCount})...");
            }
        }
    
        if (!deleted)
            Console.WriteLine($"Failed to delete Brain {i} after {maxRetries} attempts.");
    }
    
  6. 修复线程管理逻辑的漏洞
    StartThreads中轮询ThreadState的方式不可靠,线程可能处于WaitSleepJoin等中间状态,建议用Thread.Join()等待线程完成,避免索引错位的问题。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 22:37:01