Допустим, у меня есть такой код:
class Test
{
WeakReference m_ref;
public Test()
{
Test1();
Test2();
}
void Test1()
{
m_ref = new WeakReference(new object());
}
void Test2()
{
// If I do the GC then the m_ref.Target is null
// GC.Collect();
Debug.Log(m_ref.Target);
}
}
void TestFunc()
{
new Test();
}
В этом примере я создал новый экземпляр объекта и установил его как экземпляр WeakReference
в Test1
. Если я правильно понимаю, после выхода из Test1
на экземпляр объекта не будет ничего, поэтому этот экземпляр скоро будет GC
.
Однако в Test2
, если GC
не выполняется, я все еще могу получить доступ к экземпляру объекта через m_ref.Target
.
Могу ли я как-нибудь узнать, что m_ref.Target
недействителен без выполнения вручную GC
?
4 ответа
Нет, не можешь. По своей конструкции WeakReference
тесно связан со сборщиком мусора. Об этом упоминается даже в документации:
Получает информацию о том, был ли объект, на который ссылается текущий объект WeakReference , был произведен сборщиком мусора.
Насколько мне известно, в C # нет способа узнать, есть ли еще живые ссылки на данный объект, за исключением, возможно, ручного просмотра всего дерева ссылок (и в значительной степени повторной реализации GC самостоятельно).
Могу ли я узнать, что m_ref.Target недействителен, без ручного выполнения GC?
Это не недействительно, пока сборщик мусора не соберет объект. Суть сборки мусора в том, что вы не знаете и не должны заботиться о том, когда объект будет удален.
В вашем примере, да, вы правы, что после выполнения m_ref = new WeakReference(new object());
экземпляр будет собран «скоро». Однако «скоро» вообще не определено, поэтому нельзя предполагать, что это произойдет до вызова Test2
и выполнения Debug.Log(m_ref.Target);
.
Если я правильно понимаю, после выхода из Test1, на экземпляр объекта не будет никаких ссылок ...
Вы неправы. Технически у вас нет ссылки на созданный объект, он может быть указан в следующей строке.
Рассмотрим этот простой пример:
class Program
{
static WeakReference _ref;
static void Main(string[] args)
{
Test();
GC.Collect();
Console.WriteLine(_ref.IsAlive); // false
}
static void Test()
{
var obj = new object();
_ref = new WeakReference(obj);
GC.Collect();
Console.WriteLine(_ref.IsAlive); // true
}
}
В Test()
у нас есть сильная ссылка на объект, она действительно сохраняется до конца метода.
Вы можете сделать что-то подобное, чтобы быть уверенным
object obj = _ref.Target;
if (obj != null)
{
... safe to do something with obj
}
Вы не можете сказать, действительна ли WeakReference. Но вы можете сказать, недействительна ли она. Я знаю, что это странно. Если у меня есть код, который выполняет оператор if и может спросить «действителен ли он», тогда он может оказаться недействительным в следующей строке кода, поэтому он бесполезен. Вызов TryGetTarget в WeakReference получает ссылку на объект или завершается ошибкой и возвращает false. Как только у него есть ссылка, она предотвращает сборку мусора для объекта, поэтому он будет оставаться действительным, по крайней мере, пока у вас есть ссылка.
В некотором коде можно сохранить List
Отличный способ отслеживать этот список и очищать его от ссылок, на которые нет ссылок (в другом месте), - это выполнить сканирование списка с помощью цикла for с помощью TryGetTarget и, если он не удастся, удалить устаревшую ссылку из списка. Но подобное удаление разрушает итератор, поэтому вы хотите использовать RemoveAll с предикатом. У меня есть класс CognateBase и статический глобальный список AllCognateBases. В CognateBase есть функция Tick (), которую я вызываю каждый раз при запуске программы. Цикл Tick - хорошее место для получения устаревших ссылок. Так что я...
public static void TickAll()
{
// This will loop through all CognateBase objects and call their Tick, or if deleted from memory, remove the CognateBase.
AllCognateBases.RemoveAll(_TickIfAble);
}
И тогда _TickIfAble будет
private static bool _TickIfAble(WeakReference<CognateBase> r)
{
CognateBase cb;
if (r.TryGetTarget(out cb))
{
cb.Tick();
return false;
}
else
{
return true;
}
}
Таким образом, действительные экземпляры CognateBase помечаются галочкой, а недействительные удаляются. И поскольку он находится в RemoveAll, нет итератора, который мог бы запутаться.
В любом другом месте кода, где у меня есть ссылка на CognateBase и установлено значение null, CognateBase в конечном итоге будет удален и удален из списка.
И этот тест работает. Все эти вызовы GC должны заставить сборку мусора произойти СЕЙЧАС, а не через какое-то время, когда C # почувствует это.
public static void UnitTest()
{
Debug.Assert(AllCognateBases.Count == 0);
CognateBase b1 = new CognateBase();
Debug.Assert(AllCognateBases.Count == 1);
CognateBase b2 = new CognateBase();
Debug.Assert(AllCognateBases.Count == 2);
GC.Collect();
Debug.Assert(AllCognateBases.Count == 2);
b1 = null;
GC.Collect();
GC.WaitForFullGCComplete();
GC.WaitForPendingFinalizers();
TickAll();
GC.Collect();
GC.WaitForFullGCComplete();
GC.WaitForPendingFinalizers();
Debug.Assert(AllCognateBases.Count == 1);
b2 = null;
GC.Collect();
GC.WaitForFullGCComplete();
GC.WaitForPendingFinalizers();
TickAll();
GC.Collect();
GC.WaitForFullGCComplete();
GC.WaitForPendingFinalizers();
Debug.Assert(AllCognateBases.Count == 0);
}
И создатель ...
public CognateBase()
{
AllCognateBases.Add(new WeakReference<CognateBase>(this));
}
ПРЕДУПРЕЖДЕНИЕ - если для ссылки установлено значение NULL, как указано выше b1 = null; объекты могут не собираться мусором в течение looooooong времени. Все это время он еще действителен и получит вызов Tick!
Похожие вопросы
Новые вопросы
c#
C# (произносится как «see Sharp») — это высокоуровневый мультипарадигменный язык программирования со статической типизацией, разработанный Microsoft. Код C# обычно нацелен на семейство инструментов и сред выполнения Microsoft .NET, которое включает в себя .NET, .NET Framework, .NET MAUI и Xamarin среди прочих. Используйте этот тег для ответов на вопросы о коде, написанном на C#, или о формальной спецификации C#.