Pull to refresh

Comments 20

Все еще непонятно, правда, почему при запуске в VS всегда работает.
Вы открыли для себя оптимизатор.

var sa = new A();
var wa = new WeakReference<A>(new A());
Видимо имелось ввиду new WeakReference<A>(sa)
Вы открыли для себя оптимизатор

Visual Studio при запуске подавляет какие-то оптимизации?

Видимо имелось ввиду new WeakReference<A>(sa)

Конечно, спасибо за замечение, недоглядел.
Да, для работы таких фич как Edit & Continue, Intermediate window, Locals и др.
У вас либо опечатка в TestWeakRef, либо, в общем-то, он и должен падать: вы пишете «var wa = new WeakReference(new A());», а должно было бы быть, по логике, «var wa = new WeakReference(sa);». То, что после GC удаляется объект «new A()», на который есть только слабая ссылка, вполне нормально, как я понимаю. Не уверен, как это соотносится с вашей исходной проблемой. [Edit: а мне надо обновлять комментарии перед отправкой своего]
Но в любом случае, после фразы «В Visual Studio выдает «not null» при запуске вне студии выдает «null».» я ожидал самого интересного, а вы уже закончили статью. Даже если бы TestWeakRef был правильным, хотелось бы прочитать больше, с документацией GC, Unity, с объяснением, почему в Visual Studio не падает и т.п.
Даже если будет new WeakReference(sa); — поведение не поменяется ;)
Какой-то костыль. Вы воспользовались Unity (не самый лучший выбор) только из-за того, что он умеет сам подобрать нужный конструктор, а потом ещё к костылю приделали другой костыль в виде KeepAlive.
Главная мысль — время жизни объектов не соответствует времени жизни переменных, ссылающихся на эти объекты. Об этом стоит помнить, особенно если в проекте есть вызовы unmanaged кода или COM-объектов — в этом случае, ошибки буду куда более слабодиагностируемые.
Недавно читал в блоге SergeyT пост на данную тему. Там все подробно объясняется http://sergeyteplyakov.blogspot.nl/2013/08/blog-post_27.html
UFO just landed and posted this here
Нет, не избавляет.
UFO just landed and posted this here
Упрощенно проблемный код выглядит так:
void M(A a) {
    f(a);
    g();
}
Здесь существует аргумент-ссылка на инстанс класса А, который передается в функцию f, которая сохраняет исключительно слабую ссылку, и функция g, которая использует эту слабую ссылку. Кроме самой переменной-аргумента «сильных» ссылок на инстанс нет.

Проблема в том, что для программиста не всегда очевидно, что данные из f в g передаются слабой ссылкой, а время жизни локальной переменной может быть ограничено её последним использованием.

То есть, как только в методе выше завершится вызов функции f инстанс класса может быть уничтожен и более не быть достижимым в функции g.

Решается продлением времени жизни переменной-аргумента, а, соответственно, и инстанса, специально созданным для этого костылём в виде GC.KeepAlive:
void M(A a) {
    f(a);
    g();
    GC.KeepAlive(a);
}
Есть другое наблюдение, которое не понятно.
То есть, как только в методе выше завершится вызов функции f инстанс класса может быть уничтожен

Ситуация аналогичная для кода, вызывающего функцию M(a).
Пока не завершится ф-ция M, вызывающий её код держит ссылку на экземпляр a и он не должен быть уничтожен.
Вызывающий код мог быть переписан компилятором в M(new A()). А учитывая, что в примере автора вообще Tail Call, оптимизация которого скорее всего отключается при запуске из-под дебаггера, там вообще что угодно могло произойти.
Ещё: насчет «завершения функции» я несколько неочевидно выразился. Вызывающий метод вполне может потерять ссылку на какой-то объект сразу после вызова метода, если используются хитрости со стеком и регистрами. По конвенции вызовов x64 первые 4 аргумента в стек писать не обязательно. Поэтому теоретически JIT мог переписать вызов M как вызов с поглощением стекового пространства каких-то переменных, оставив аргументы в регистрах. А для Value Types там другая картина, они могут быть «размазаны» по стеку угодным компилятору образом, и там, опять же, чёрт знает что происходит.
Если ссылка находится находится в локальной переменной, а оптимизатор переместил её в регистр, GC может уничтожить объект? Это баг.
Нет, в общем случае для GC нет разницы, где находится ссылка — на стеке или в регистре, он отследит и то, и то. Но даже нахождение ссылки на стеке может не спасти инстанс от уничтожения после вызова метода (до завершения) — то есть, после последнего использования.
Так все правильно. У вас в последнем примере кода sa на момент GC.Collect() уже никем не держится. В методе на нее ссылок нет.

Другой вопрос зачем в юнити используются слабые ссылки. Это жутко неэффективно. Их же периодически удалять нужно, когда объекты умирают, иначе разрастется внутренняя структура данных.
Переменная sa существует до конца метода. В моем представлении, объект, на который она ссылается тоже должен был бы существовать до конца метода.

А в Unity ссылки слабые, потому что был использован ExternallyControlledLifetimeManager, другие life time менеджеры их не используют, но в том месте, контейнер короткоживущий, поэтому отдавать ему возможность управлять временем жизни объектов было не нужно.
Sign up to leave a comment.

Articles