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

C#工作线程迭代遍历目录:异步方法抛ThreadAbortException问题排查

问题分析与修复方案

核心问题

  1. 异步方法返回值错误:MapLocalFilesAsync使用async void声明,单元测试方法是同步的,测试框架不会等待异步任务完成,测试方法执行完毕后直接终止进程,导致后台运行的TraverseFolder被强制中断,抛出ThreadAbortException。
  2. 集合线程安全隐患:虽然用了ConcurrentDictionary,但它仅保证键的线程安全,对应的List<string>不是线程安全集合,多线程操作时可能出现元素丢失或异常(当前场景下因进程终止先触发异常,但这是潜在问题)。

分步修复

1. 修正异步方法的返回类型

将async void改为async Task,这样测试方法可以通过await等待异步操作完成:

public async Task MapLocalFilesAsync()
{
    await Task.Run(() => TraverseFolder(rootFolder));
    Debug.WriteLine(nameof(MapLocalFilesAsync) + " has terminated");
}

2. 修改单元测试为异步模式

测试方法需改为async Task类型,并使用await等待异步方法执行完毕:

[TestMethod]
public async Task TestFolderMapperAsync()
{
    FileMapper fileMapper = new FileMapper(@"D:\temp");
    await fileMapper.MapLocalFilesAsync();
}

3. 修复集合的线程安全问题

将List<string>替换为线程安全的ConcurrentBag<string>,确保多线程场景下添加元素的安全性:

// 修改Files属性定义
private ConcurrentDictionary<string, ConcurrentBag<string>> Files { get; } = new ConcurrentDictionary<string, ConcurrentBag<string>>(StringComparer.OrdinalIgnoreCase);

// 替换TraverseFolder中的文件路径添加逻辑
if (!Files.TryGetValue(filename, out var paths))
{
    paths = new ConcurrentBag<string>();
    Files.TryAdd(filename, paths);
}
paths.Add(filePath);

如果需要保留List<string>,也可以通过加锁实现线程安全:

// 保留原Files定义
private ConcurrentDictionary<string, List<string>> Files { get; } = new ConcurrentDictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);

// 修改添加逻辑
var fileList = Files.GetOrAdd(filename, _ => new List<string>());
lock (fileList)
{
    fileList.Add(filePath);
}

完整修复代码

FileMapper类

public class FileMapper
{
    private string rootFolder;

    public FileMapper(string rootFolder)
    {
        this.rootFolder = rootFolder;
    }

    private ConcurrentDictionary<string, ConcurrentBag<string>> Files { get; } = new ConcurrentDictionary<string, ConcurrentBag<string>>(StringComparer.OrdinalIgnoreCase);
    
    public void MapLocalFiles()
    {
        TraverseFolder(rootFolder);
        Debug.WriteLine(nameof(MapLocalFiles) + " has terminated");
    }

    public async Task MapLocalFilesAsync()
    {
        await Task.Run(() => TraverseFolder(rootFolder));
        Debug.WriteLine(nameof(MapLocalFilesAsync) + " has terminated");
    }

    private void TraverseFolder(string dirPath)
    {
        string[] filesPaths = Directory.GetFiles(dirPath);

        foreach (var filePath in filesPaths)
        {
            string filename = Path.GetFileName(filePath);
            
            Debug.WriteLine(filePath);

            if (!Files.TryGetValue(filename, out var paths))
            {
                paths = new ConcurrentBag<string>();
                Files.TryAdd(filename, paths);
            }
            paths.Add(filePath);
        }

        var directories = Directory.GetDirectories(dirPath);

        foreach (var directory in directories)
        {
            TraverseFolder(directory);
        }
    }
}

单元测试代码

[TestMethod]
public async Task TestFolderMapperAsync()
{
    FileMapper fileMapper = new FileMapper(@"D:\temp");
    await fileMapper.MapLocalFilesAsync();
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 13:26:32