invalidoperationexception的根本原因是向已调用completeadding()的blockingcollection再次添加元素;2. 解决方案包括确保completeadding()仅在所有生产者完成时调用,避免后续add()操作,使用countdownevent或锁协调多生产者;3. 消费者应优先使用foreach结合getconsumingenumerable()来优雅退出;4. 常见误区包括未调用completeadding()、在完成后仍add()、未处理异常和内存溢出,规避策略为使用容量限制、异常处理和同步机制确保生命周期正确管理,从而保证生产-消费流程的稳定结束。

当C#的
BlockingCollection
InvalidOperationException
解决
BlockingCollection
InvalidOperationException
CompleteAdding()
首先,要明确
CompleteAdding()
Add()
InvalidOperationException
核心解决策略:
生产者端:
CompleteAdding()
CompleteAdding()
Add()
CompleteAdding()
Add()
消费者端:
foreach
foreach (var item in blockingCollection)
CompleteAdding()
Add()
Add()
CompleteAdding()
通常,这种异常的出现,意味着你的生产者和消费者之间的“协议”出了问题。生产者以为自己还有活儿要干,或者忘记了自己已经“退休”了。
说实话,遇到这种异常,我第一反应常常是:“又是在哪个角落里漏掉了状态判断?”
BlockingCollection
InvalidOperationException
典型场景分析:
生产者逻辑错误: 最常见的情况是,你的生产者线程在完成所有数据生产后,确实调用了
CompleteAdding()
Add()
BlockingCollection<int> collection = new BlockingCollection<int>();
// 生产者任务
Task.Run(() =>
{
for (int i = 0; i < 5; i++)
{
collection.Add(i);
Thread.Sleep(100);
}
collection.CompleteAdding(); // 标记完成
// 假设这里有个bug,或者某个异常分支导致了再次添加
try
{
// 模拟一个不应该发生的添加
collection.Add(999); // 这里会抛出 InvalidOperationException
}
catch (InvalidOperationException ex)
{
Console.WriteLine($"捕获到异常:{ex.Message}");
}
});
// 消费者任务
Task.Run(() =>
{
foreach (var item in collection.GetConsumingEnumerable())
{
Console.WriteLine($"消费了:{item}");
}
Console.WriteLine("消费者完成。");
}).Wait(); // 等待消费者完成,以便观察异常多生产者竞态条件: 如果你有多个生产者线程,它们都可能在各自完成任务后尝试调用
CompleteAdding()
CompleteAdding()
CompleteAdding()
Add()
不恰当的异常处理: 有时候,代码中的
catch
BlockingCollection
外部依赖的副作用: 你的生产者可能依赖于外部事件或回调。如果这些外部事件在
CompleteAdding()
Add()
理解这些场景有助于你定位问题,因为这种异常很少是
BlockingCollection
确保生产者正确地停止向
BlockingCollection
InvalidOperationException
CompleteAdding()
单生产者场景:
collection.CompleteAdding()
void ProduceDataSingleProducer(BlockingCollection<string> collection)
{
try
{
for (int i = 0; i < 10; i++)
{
collection.Add($"Data item {i}");
Thread.Sleep(50); // 模拟生产耗时
}
}
finally
{
// 确保无论如何都调用CompleteAdding,即使发生异常
collection.CompleteAdding();
Console.WriteLine("单生产者:所有数据已添加,并标记完成。");
}
}这里使用
finally
CompleteAdding()
多生产者场景:
协调机制: 这是复杂性增加的地方。你需要一个机制来协调所有生产者,确保只有当所有生产者都完成其任务后,才调用
CompleteAdding()
Interlocked.Decrement
CountdownEvent
CompleteAdding()
// 示例:使用CountdownEvent协调多生产者 BlockingCollection<string> sharedCollection = new BlockingCollection<string>(); int producerCount = 3; CountdownEvent allProducersDone = new CountdownEvent(producerCount);
void MultiProducerTask(int id) { try { for (int i = 0; i < 5; i++) { sharedCollection.Add($"Producer {id} - Item {i}"); Thread.Sleep(new Random().Next(20, 100)); } Console.WriteLine($"生产者 {id} 完成其生产任务。"); } finally { allProducersDone.Signal(); // 信号通知自己已完成 } }
// 启动生产者 for (int i = 0; i < producerCount; i++) { Task.Run(() => MultiProducerTask(i)); }
// 等待所有生产者完成 Task.Run(() => { allProducersDone.Wait(); // 阻塞直到所有生产者都发出信号 sharedCollection.CompleteAdding(); Console.WriteLine("所有生产者已完成,集合标记为完成添加。"); });
状态标志与锁: 在更复杂的场景中,你可能需要一个共享的布尔标志和锁来控制
Add()
CompleteAdding()
true
Add()
避免冗余调用:
CompleteAdding()
核心思想是:
CompleteAdding()
消费者端处理
BlockingCollection
BlockingCollection
使用foreach
BlockingCollection
IEnumerable<T>
foreach
CompleteAdding()
foreach
void ConsumeData(BlockingCollection<string> collection)
{
Console.WriteLine("消费者:开始消费数据...");
try
{
foreach (var item in collection.GetConsumingEnumerable()) // 推荐使用此方法
{
Console.WriteLine($"消费者:处理 '{item}'");
Thread.Sleep(new Random().Next(50, 200)); // 模拟消费耗时
}
Console.WriteLine("消费者:所有数据已消费完毕,循环正常退出。");
}
catch (OperationCanceledException)
{
Console.WriteLine("消费者:操作被取消。");
}
catch (Exception ex)
{
Console.WriteLine($"消费者:发生未知异常 - {ex.Message}");
}
}GetConsumingEnumerable()
Take()
CompleteAdding()
使用TryTake()
CancellationToken
TryTake()
CancellationToken
void ConsumeDataWithCancellation(BlockingCollection<string> collection, CancellationToken cancellationToken)
{
Console.WriteLine("消费者 (带取消):开始消费数据...");
try
{
while (!cancellationToken.IsCancellationRequested)
{
string item;
// 尝试取出数据,带超时和取消令牌
if (collection.TryTake(out item, TimeSpan.FromMilliseconds(100), cancellationToken))
{
Console.WriteLine($"消费者 (带取消):处理 '{item}'");
}
else
{
// 如果TryTake返回false,表示在超时时间内没有数据
// 检查集合是否已完成且为空
if (collection.IsCompleted)
{
Console.WriteLine("消费者 (带取消):集合已完成且为空,退出。");
break; // 集合已完成且为空,退出循环
}
// 否则,只是暂时没有数据,可以做其他事情或继续等待
Console.WriteLine("消费者 (带取消):暂时没有数据,等待中...");
}
}
}
catch (OperationCanceledException)
{
Console.WriteLine("消费者 (带取消):操作被取消。");
}
catch (Exception ex)
{
Console.WriteLine($"消费者 (带取消):发生未知异常 - {ex.Message}");
}
}这种方式需要手动检查
IsCompleted
IsCompleted
CompleteAdding()
true
TryTake()
CancellationToken
TryTake()
OperationCanceledException
选择哪种方式取决于你的具体需求。对于大多数简单的生产-消费场景,
foreach
TryTake()
CancellationToken
生产-消费模式,尤其是用
BlockingCollection
误区:忘记调用CompleteAdding()
BlockingCollection
Take()
CompleteAdding()
finally
CountdownEvent
误区:在CompleteAdding()
Add()
InvalidOperationException
CompleteAdding()
Add()
if (!collection.IsAddingCompleted)
Add()
CompleteAdding()
误区:消费者在BlockingCollection
Take()
OperationCanceledException
IsCompleted
Take()
GetConsumingEnumerable()
CancellationToken
IsCompleted
CompleteAdding()
Take()
InvalidOperationException
CancellationToken
OperationCanceledException
foreach (var item in collection.GetConsumingEnumerable())
Take()
CancellationToken
catch (OperationCanceledException)
!collection.IsCompleted
误区:生产者和消费者之间的数据量不匹配导致内存问题
BlockingCollection
BlockingCollection
new BlockingCollection<T>(capacity)
Add()
误区:在生产者或消费者内部发生未处理的异常
BlockingCollection
try-catch-finally
finally
CompleteAdding()
总之,
BlockingCollection
以上就是C#的BlockingCollection的InvalidOperationException怎么处理?的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号