C#工作线程迭代遍历目录:异步方法抛ThreadAbortException问题排查
问题分析与修复方案
核心问题
- 异步方法返回值错误:
MapLocalFilesAsync使用async void声明,单元测试方法是同步的,测试框架不会等待异步任务完成,测试方法执行完毕后直接终止进程,导致后台运行的TraverseFolder被强制中断,抛出ThreadAbortException。 - 集合线程安全隐患:虽然用了
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
相关产品推荐
相关产品推荐

