У меня есть огромный набор тестов сборки CI, более 3000 тестов.Для параллельного запуска тестов я использую AsyncLocal<T>
объекты.Проблема в том, что с тех пор, как я начал это делать, сборочный бот CI завершается примерно на 75%, а затем разбомбляется, потому что ему не хватает памяти.Нет другого виновника, на которого можно было бы указать пальцем, кроме недавно представленных AsyncLocal<T>
объектов, которые используются для хранения чего-либо от логических значений до сложных объектов.
Я предполагаю, что каким-то образом злоупотребляю системой, иесть некоторый «правильный» способ использования AsyncLocal<T>
, чтобы его значения собирались мусором после завершения текущего контекста.
Может быть полезно знать, что до того, как я обнаружил существование AsyncLocal<T>
, я пыталсяреализовать эту функциональность самостоятельно, используя свой собственный класс, называемый Parallelizable<T>
.Это работало с использованием TestExecutionContext.CurrentContext.CurrentTest.FullName
в NUnit, но было медленным, как патока, и затем я копался в базовом коде и обнаружил, что он использует AsyncLocal<>
, поэтому я решил вырезать мою реализацию и изменить Parallelizable<T>
, чтобы она была оберткой вокругAsyncLocal<T>
, чтобы я мог применить минимальное насилие к моим существующим тестам.И вот тогда я начал выходить из строя памяти.
Как правильно обеспечить, чтобы эти значения были GC'd?
Вот мой код для Parallelizable<T>
, FWIW:
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;
}
}
}
ОБНОВЛЕНИЕ: Я обошел проблему, найдя место в базовом классе моего тестового прибора, которое наиболее интенсивно использовало AsyncLocal<>
для крупных объектов и внутри TearDown()
Метод удостоверился, что удалил и установил значение свойств AsyncLocal
равным null
.Это решило мою проблему.
Однако вопрос остается открытым: почему, когда сами объекты AsyncLocal<>
были GC'd, все их значения также не получали GC'd?Кажется, это ошибка в реализации AsyncLocal<>
, не так ли?