使用SemaphoreSlim限制C#并行任务数量时触发ObjectDisposedException异常的问题排查与解决
问题根源分析
你遇到的ObjectDisposedException主要有两个核心原因:
任务数组中存在未初始化的
null元素
你的taskArray是按recorder.Channels.Count固定长度创建的,但只有当test(channel.Name)返回true时才会赋值任务并递增index。这意味着数组中会存在大量null值,当调用Task.WaitAll(taskArray)时,会触发ArgumentException(因为不允许等待null任务)。这个未被捕获的异常会直接让程序退出using块,导致SemaphoreSlim被提前释放(Dispose)。而此时正在运行的任务在finally块中调用concurrencySemaphore.Release()时,就会触发ObjectDisposedException。迭代变量捕获的潜在问题
在foreach循环中直接捕获channel变量,在C# 5之前的版本中,迭代变量是在循环外部声明的,所有任务会共享同一个引用,导致所有任务处理的都是最后一个IDataChannel实例。虽然这不是当前异常的直接原因,但会导致业务逻辑错误,必须提前修复。
修复后的代码示例
int maxTasks = 2; using (SemaphoreSlim concurrencySemaphore = new SemaphoreSlim(maxTasks)) { // 用动态列表存储实际要执行的任务,避免null元素 List<Task<int>> tasks = new List<Task<int>>(); foreach (IDataChannel channel in recorder.Channels) { if (test(channel.Name)) { concurrencySemaphore.Wait(); // 创建局部变量捕获当前channel,避免引用共享问题 IDataChannel currentChannel = channel; Task<int> task = Task<int>.Factory.StartNew(() => { int resTask = -1; try { resTask = ProcessChannel(currentChannel, id, filename, recorder.Name); } finally { // 确保信号量一定会被释放,即使任务执行失败 concurrencySemaphore.Release(); } return resTask; }); tasks.Add(task); } } // 只等待实际创建的任务,避免null导致的异常 Task.WaitAll(tasks.ToArray()); } // 处理任务结果 int res = 0; foreach (var task in tasks) { int result = task.Result; if (result == -1) { res = -1; break; } }
额外优化建议
- 优先使用异步等待:如果你的代码运行在异步方法中,建议用
await Task.WhenAll(tasks)代替Task.WaitAll,避免阻塞主线程,提升程序响应性。 - 添加等待超时机制:可以给
SemaphoreSlim.Wait()添加超时时间,比如concurrencySemaphore.Wait(TimeSpan.FromSeconds(30)),避免主线程无限等待。异步场景下可以用await concurrencySemaphore.WaitAsync(TimeSpan.FromSeconds(30))。 - 异常处理增强:可以在任务内部添加更详细的异常捕获逻辑,方便排查
ProcessChannel方法中可能出现的问题。
内容的提问来源于stack exchange,提问作者Nebuchanazer
相关产品推荐
相关产品推荐

