我有一个庞大的CI构建测试套件,超过3,000个测试。为了并行运行测试,我大量使用了AsyncLocal<T>
对象。麻烦的是,自从我开始这样做以来,CI构建机器人已完成了大约75%,然后由于内存不足而炸裂了。除了新引入的AsyncLocal<T>
对象(用于存储从布尔值到复杂对象的任何东西)之外,没有其他罪魁祸首。
我想我正在以某种方式滥用系统,并且有一种使用“ AsyncLocal<T>
”的“适当”方法,以便一旦当前上下文终止,其值就会被垃圾回收。
在我发现AsyncLocal<T>
存在之前,我试图自己使用自己的名为Parallelizable<T>
的类来实现此功能可能会有所帮助。可以使用NUnit的TestExecutionContext.CurrentContext.CurrentTest.FullName
来实现,但是它的速度很慢,所以我深入研究了底层代码,发现它正在使用AsyncLocal<>
,所以我决定削减实现并更改{{1} }作为Parallelizable<T>
的包装,这样我就可以对现有测试进行最小程度的暴力测试。那是我开始摆脱内存故障的时候。
什么是确保这些值经过GC处理的正确方法?
这是我AsyncLocal<T>
的代码,FWIW:
Parallelizable<T>
更新:我通过在测试夹具基类中找到了使用 public class Parallelizable<T>
{
private readonly AsyncLocal<T> _asyncLocal = new AsyncLocal<T>();
private readonly AsyncLocal<bool> _valueSet = new AsyncLocal<bool>();
private readonly T _defaultValue;
private readonly Func<T> _defaultFunc;
public Parallelizable()
{
_valueSet.Value = false;
}
public Parallelizable(T defaultValue)
{
_defaultValue = defaultValue;
}
public Parallelizable(Func<T> defaultFunc)
{
_defaultFunc = defaultFunc;
}
public T Value
{
get
{
if (!_valueSet.Value)
{
_asyncLocal.Value = _defaultFunc != null ? _defaultFunc.Invoke() : _defaultValue;
_valueSet.Value = true;
}
return _asyncLocal.Value;
}
set
{
_asyncLocal.Value = value;
_valueSet.Value = true;
}
}
}
和大型对象的密集位置,并在{{1} }方法确保将AsyncLocal<>
属性的值设置为TearDown()
。这解决了我的问题。
但是,问题仍然悬而未决:为什么AsyncLocal
对象本身被GC'd了,它们的所有值也都未被GC'd了?这似乎是null
实现中的错误,不是吗?