ConcurrentQueue<StrongBox<T>> 的用法

11
我基本上正在寻找一个图像集合的容器,这些图像是从相机中在线程中获取的。由于ConcurrentQueue是线程安全的,所以我想使用它。但是在调试我的代码时,我发现了此文章所说的内容。
如果元素很小,您可能永远不会注意到这一点。然而,如果元素保持大量资源(例如每个元素都是巨大的图像位图),则有可能会看到其影响(一种解决方法是排队包装对象,例如使用ConcurrentQueue<StrongBox<T>>而不是ConcurrentQueue<T>,并且在包装器被出列后将包装器对T值的引用设置为null)。
就我所看到的,StrongBox是原始值的一种包装器。这是否意味着我必须存储另一个图像集合?

我正在寻找关于ConcurrentQueue<StrongBox<T>>的使用方法或示例。我在谷歌上只找到了这段代码


1
您是否在分析代码时发现问题,表明对这些图像的引用被保留的时间显着长于应该的时间?虽然本文所描述的问题可能会影响您,但它不太可能是一个问题。您需要以非常特定的方式使用队列才能引起问题(例如,长时间保留许多打开的迭代器,具有非常大的元素和非常缓慢的出队速率,没有任何对大型项目的其他引用等)。 - Servy
@Servy 谢谢您的评论。我还没有对我的代码进行分析,但是在那部分中图像处理比我预期的要慢。我不认为我的集合会很多(希望最多只有10-20个),但每个图像都很大(2048x2048)。所以我的下一项工作将是进行分析。 :-) - Tae-Sung Shin
1
如果您遇到此问题,它不会对程序的速度产生任何影响。但这可能意味着您的程序在使用某些内存时比必要的时间更长。 - Servy
2
StrongBox是一种将可能的大值类型值转换为引用的晦涩方式。它被System.Linq.Expressions使用,这是一个不能对数据进行假设的命名空间。它有助于图像的可能性应该为零。 - Hans Passant
1
同一篇文章提到:“对于.NET 4.5,我们改变了设计,以达到我们认为的良好平衡。除非存在并发枚举,否则已出队元素现在将被置空。”因此,您也可以升级到最新的框架,甚至不必担心这个问题,尽管这可能根本不是问题。 - Mani Gandham
1个回答

9

在提醒了过早进行优化的危险性之后,我将解释这里发生的语义。

正如文章所指出的那样,ConcurrentQueue 可以保留已经通过它的一些内容的引用。我学习时是“几十个”,而文章说最多不超过 31 个,这似乎相当合理。如果队列正在跟踪大对象,比如你的 2000x2000 位图,理论上可能会成为一个问题。当然,这取决于程序的其他部分在做什么。

将其包装在 StrongBox<T> 中有所帮助,因为 StrongBox 的唯一作用就是保留对其他东西的引用。因此,StrongBox 具有非常小的占用空间,它所持有的任何内容将更快地超出范围并(理论上)被 GC 回收。

由于 StrongBox 拥有所有含量为低热量饮料的内容,所以你有点过度考虑其使用。你只需将 Value 字段加载到某个 T 中,然后稍后引用它即可。代码看起来有点像这样:

var boxedBitmap = new StrongBox<Bitmap>(new Bitmap(1,1));
var bitmap = boxedBitmap.Value;

或者另一种选择:
var boxedBitmap = new StrongBox<Bitmap>();
boxedBitmap.Value = new Bitmap(1,1);
var bitmap = boxedBitmap.Value;

严格地说,如果你在Reflector中打开这个类,它的实现只有大约5行。
因此,你使用ConcurrentQueue<T>并不比使用ConcurrentQueue<StrongBox<T>>有什么不同。你只需在将资源发送到目标线程之前添加.Value即可。这确实帮助过我所在公司通过引用一个确定性工具而不是传递整个工具来大幅减少大规模多线程分析服务的内存占用,但这可能会有不同的影响——如果你正在传递一个要被修改并由其他东西使用的对象,则情况不明确。

我可能只是累了,但如果StrongBox持有一个对象的引用,而队列最多持有31个StrongBox,那么GC如何更快地访问任何东西呢?(我错过了什么吗?) - Cameron
9
我的理解是,你可以将 StrongBox 的内容设置为空,但无法访问 ConcurrentQueue 的内部以将刚刚使用的队列位置设置为空。因此,即使队列仍然保留着微小的 StrongBox,GC 也可以收集大对象。 - Bryce Wagner
我来这里搜索StrongBox,这篇文章和评论对我帮助很大,谢谢!不过在实际应用中,出于各种原因,我可能会自己编写类似StrongBox的东西。 - Eric Scherrer
5
值得一提的是,.NET 4.5 中的实现已经改变,现在完全会在出队时移除每个对象,除非正在某个地方运行着一个活动的枚举器(此时它必须保留对象以使枚举正常工作)。因此,对于常规的入队/出队用法,在出队后不会保留任何引用。 - vgru
对于您出色的解释,我只有一个非常小的修正/异议; 您引用了文章提到的“[保持]最后<=31个出队元素”,但是这种最坏情况分析仅适用于正常的非枚举操作。根据“快照”语义所要求的,每个/任何已发出/待处理的枚举器引用显然都会保留所有尚未报告的元素--数量不受限制--即使整个队列随后被清除(并且此后无限期)。无论如何,考虑到“快照”的明显含义,您的“...不超过31个...”实际上只是一种措辞上的技术性问题。 - Glenn Slayden

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接