Форум программистов, компьютерный форум, киберфорум
C# .NET
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск  
 
 
Рейтинг 4.91/11: Рейтинг темы: голосов - 11, средняя оценка - 4.91
 Аватар для INexteR
17 / 16 / 1
Регистрация: 25.01.2023
Сообщений: 468
.NET 4.x

Почему сборщик не собирает объект файлового потока

07.02.2024, 20:12. Показов 2718. Ответов 31
Метки нет (Все метки)

Студворк — интернет-сервис помощи студентам
Здравствуйте. Имеется такой код
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var cts = new CancellationTokenSource();
Task.Run(() => {
    while (!cts.IsCancellationRequested)
    {
        try
        {
            var path = "Temp.dat";
            var bytesToWrite = new byte[] { 1, 2, 3, 4, 5, 6 };
            var fs = new FileStream(path, FileMode.Create);
            fs.Write(bytesToWrite, 0, bytesToWrite.Length);
            GC.Collect();
            File.Delete(path);
            Console.WriteLine("success");
        }
        catch (IOException)
        {
            Console.WriteLine("IOException");
        }
        Thread.Sleep(1000);
    }
});
Console.ReadLine();
cts.Cancel();
Console.ReadLine();
При этом используется платформа .Net Framework 4.7.2, оптимизация JIT-компилятора включена. Из этого следует, что сборщик не продлевает время жизни переменных до окончания метода.
Вопрос, почему сборщик не всегда освобождает объект файлового потока? Предполагаю, что он не всегда успевает выполнить финализатор.
P.S. имеется понимание, что в производственном коде потоки данных закрывают вручную. Разбор данного кода проводится исключительно в учебных целях.
0
Лучшие ответы (1)
IT_Exp
Эксперт
34794 / 4073 / 2104
Регистрация: 17.06.2006
Сообщений: 32,602
Блог
07.02.2024, 20:12
Ответы с готовыми решениями:

Почему событие eof() файлового потока наступает очень поздно? Какова вообще его логика?
Вот пример, если в папке с программой разместить файл input.txt с числами "1 2 3", то в векторе sequence будут следующие элементы: 1 2 3 3 ...

Почему объект типа std::vector не читается из потока?
# include <iostream> # include <vector> # include <fstream> using namespace std; int main () {

Состояние файлового потока
.... fostream log; .... class A { public: A (); }; A::A() {

31
62 / 187 / 31
Регистрация: 14.02.2013
Сообщений: 1,716
08.02.2024, 22:10
Студворк — интернет-сервис помощи студентам
Цитата Сообщение от INexteR Посмотреть сообщение
открытый дескриптор файла удерживает файл и не дает другому процессу доступ к нему
Так если поток не завершён то сборщик мусора и не тронет этот поток, надо с начало завершить объект потока, потом закрыть дескриптор этого потока и уже потом вызвать сборщик мусора вот только это лишнее ОС сама это сделает.
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16166 / 11286 / 2892
Регистрация: 21.04.2018
Сообщений: 33,175
Записей в блоге: 2
08.02.2024, 23:39
Цитата Сообщение от Wolfdp Посмотреть сообщение
Выше по стеку у вас есть ссылка на этот объект. Вопрос -- почему GC должен решить что ссылок больше нет, и объект можно грохать?
Нет ссылок ниже.
При оптимизации кода компилятор может решить, что раз нет дальше ссылок то сохранять объект не нужно.

То есть по факту он может быть преобразован в такой:
C#
9
10
11
            // var fs = new FileStream(path, FileMode.Create);
            // fs.Write(bytesToWrite, 0, bytesToWrite.Length);
            new FileStream(path, FileMode.Create).Write(bytesToWrite, 0, bytesToWrite.Length);
0
Эксперт .NET
 Аватар для Wolfdp
3790 / 1767 / 371
Регистрация: 15.06.2012
Сообщений: 6,543
Записей в блоге: 3
08.02.2024, 23:52
Цитата Сообщение от Элд Хасп Посмотреть сообщение
То есть по факту он может быть преобразован в такой:
Если я правильно помню, то все эти сокращения неявно создают переменные. Тоже самое и для передаваемых значений, когда мы вместо них подставляем методы или конструкторы. Но это не точно. Может на самом низком уровне и происходят какие-то финты ушами, что GC (который ой как не охотно зачищает память, при наличии лишней в ОЗУ) таки зачищает память.

Цитата Сообщение от VladimirU Посмотреть сообщение
Так если поток не завершён то сборщик мусора и не тронет этот поток
Вот пример с таки мертвой кошкой в коробке, хотя никто её не трогал
0
Эксперт .NET
 Аватар для Wolfdp
3790 / 1767 / 371
Регистрация: 15.06.2012
Сообщений: 6,543
Записей в блоге: 3
09.02.2024, 00:19
На всякий -- с FileStream пример выше не применим по этой причине
C#
1
2
3
4
5
6
7
8
[System.Security.SecuritySafeCritical]  // auto-generated
        ~FileStream()
        {
            if (_handle != null) {
                BCLDebug.Correctness(_handle.IsClosed, "You didn't close a FileStream & it got finalized.  Name: \""+_fileName+"\"");
                Dispose(false);
            }
        }
Добавлено через 20 минут
В догонку х2: под "с FileStream пример выше не применим" подразумевал что поток будет закрываться, так как в деструкторе явный вызов Dispose, и фиг ты это как-то выпилишь (разве что переопределять сам Dispose, смотреть поток вызова и игнорить, что попахивает читерством).

Важно другое -- потеряли ссылку на объект -> GC в неровен час объект приговорит. То что это открытый файл -- погоды не делает. Можно заменить мой код на это, и увидеть что кошка ровно также умирает, хотя файл никто явно не закрывал.
C#
1
2
3
4
5
6
7
8
9
10
11
class Neko : FileStream, IDisposable
{
    ~Neko()
    {
        Console.WriteLine("Die");
    }
 
    public Neko()
        : base(@"D:\temp\123.txt", FileMode.OpenOrCreate)
    { }
}
1
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16166 / 11286 / 2892
Регистрация: 21.04.2018
Сообщений: 33,175
Записей в блоге: 2
09.02.2024, 09:11
Цитата Сообщение от Wolfdp Посмотреть сообщение
Если я правильно помню, то все эти сокращения неявно создают переменные.
При Release оптимизации необязательно.
Но здесь возможна разница из-за фрейм платформ (Frame vs Core), из-за CLR на разных ОС.
То есть, условно, код может работать на WibDesk без всяких багов, а на маках нет нет выкидывать исключения.

По факту, то о чём написал INexteR, является багом. Так как никакая оптимизация не должна приводит к нестабильно работающему коду. Причины этой нестабильности поняты: какой-то из компиляторов не может уловить, что fs удерживает тот же файл что передаётся в метод Delete. Поэтому не удерживает ссылку на fs, что в исключительно редких случаях приводит к другому результату выполнения метода. В Core это уже пофикшено. Скорее всего не конкретно для потоков, а решение общее - все объекты сохраняемые в локальных переменных удерживаются до завершения работы метода независимо от оптимизации кода.
Во Framework это не пофиксили, так как платформа официально уже не развивается.

Добавлено через 2 минуты
И как написал INexteR: Разбор данного кода проводится исключительно в учебных целях.
То есть речь не о том, как правильно писать НА ПРАКТИКЕ. ТС это знает.
А просто демонстрация и обсуждение одного из багов, который на практике может встретиться в исключительно редких случаях.
0
Эксперт .NET
 Аватар для Wolfdp
3790 / 1767 / 371
Регистрация: 15.06.2012
Сообщений: 6,543
Записей в блоге: 3
09.02.2024, 09:24
Элд Хасп,
всё же мне кажется что если в стеке если переменная, и на этом уровне есть вызов GC -- магии не случится. Разве что компилятор решит влепить fs = null перед вызовом GC (вот этого не исключаю).

Если же более обобщать: вызов GC в общем случае не приводить к моментальной финализации объекта. Ну т.е. это вот вообще ни разу не аналог delete из C++, а судя по примеру -- ожидалось нечто такое в поведении.

Плюс ещё такой момент -- в финализацию объекта можно запихнуть столько кода, что убиваться он будет долго. К слову хороший вопрос -- если я влеплю Sleep -- GC будет ждать, и только потом приступит к остальным объектам?
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16166 / 11286 / 2892
Регистрация: 21.04.2018
Сообщений: 33,175
Записей в блоге: 2
09.02.2024, 18:54
Цитата Сообщение от Wolfdp Посмотреть сообщение
всё же мне кажется что если в стеке если переменная, и на этом уровне есть вызов GC -- магии не случится. Разве что компилятор решит влепить fs = null перед вызовом GC (вот этого не исключаю).
ТС описывает реальный баг и как его воспроизвести.
Другое дело, что на практике такая ситуация "один на миллион".
Вполне возможно, что при оптимизации (двойной: C#->CIL->x86) на какой-то стадии решается, что ссылку на объект даже в стековую переменную сохранять не нужно. Получили в адресный регистр, вызвали метод объекта и никуда дальше адрес не сохраняют.
Чтобы в этом разобраться надо очень глубоко опускаться - возможно до уровня машинных кодов.
Это не нужно.
Просто должно быть понимание, что во Framework есть такой редкий баг, поэтому не стоит так делать.
0
Эксперт .NET
 Аватар для Wolfdp
3790 / 1767 / 371
Регистрация: 15.06.2012
Сообщений: 6,543
Записей в блоге: 3
09.02.2024, 19:33
Цитата Сообщение от Элд Хасп Посмотреть сообщение
ТС описывает реальный баг и как его воспроизвести
Цитата Сообщение от Элд Хасп Посмотреть сообщение
То есть речь не о том, как правильно писать НА ПРАКТИКЕ. ТС это знает.
Я запутался:
- либо у ТСа изначальный пример неправильный, так как он не высвобождает ресурс файла и дальше пытается его использовать в другом объекте. Так нельзя писать и точка. Сначала высвобождаем, и только потом используем по новой.
- либо это псевдо-пример, чтобы понять когда высвобождается ресурс, и тогда ссылаясь на саму документацию
Use this method to force the system to try to reclaim the maximum amount of available memory.
И в целом пробежавшись по описаниям, можно заметить что нигде не прописана 100% гарантия что после отработки всё зачистится. Исходя из этого, даже такой явный пример не рабочий
C#
1
2
3
4
var fs = new FileStream(path, FileMode.Create);
fs.Write(bytesToWrite, 0, bytesToWrite.Length);
fs = null;
GC.Collect();
То что "иногда срабатывае" -- не аргумент. В многопоточности тоже "иногда срабатывает, иногда ошибки", но мы же почему-то всегда используем объекты синхронизации? Более того, как я показывал выше -- если есть реализация Disposed, то GC её не пнет (это даже в доках прописано).
The garbage collector does not, by default, call the Dispose method; however, implementations of the Dispose method can call methods in the GC class to customize the finalization behavior of the garbage collector.
Исходя из этого, нам ВСЕГДА нужно ЯВНО высвобождать ресурс, если это подразумевает логика работы с классом, так как программист мог не прокинуть её в деструктор, а значит у нас будут утечки памяти. Причем этот мусор может оставаться даже после завершения программы. Например -- дочерний процесс, который не убивается автоматически.
1
 Аватар для INexteR
17 / 16 / 1
Регистрация: 25.01.2023
Сообщений: 468
10.02.2024, 14:57  [ТС]
Wolfdp, Приветствую, данный код служит не более чем примером для проверки на предмет того, как работает сборщик. Я сначала считал, что вызов метода Collect синхронный и что в таком случае сборщик гарантированно соберет неиспользуемый далее поток данных, вследствие чего всегда будет выводиться строка success. Но потом понял, что этот вызов асинхронный и что вызов метода Finalize не детерминирован. Его выполняет отдельный высокоприоритетный поток и он далеко не всегда успевает выполниться до конца, отпустив файл, и поэтому чаще выводится IOException. Но если сразу после вызова Collect вызвать GC.WaitForPendingFinalizers или Thread.Sleep(скажем, 1000), то всегда будет выводиться success.
0
Эксперт .NET
 Аватар для Wolfdp
3790 / 1767 / 371
Регистрация: 15.06.2012
Сообщений: 6,543
Записей в блоге: 3
10.02.2024, 16:28
Цитата Сообщение от INexteR Посмотреть сообщение
Но если сразу после вызова Collect вызвать GC.WaitForPendingFinalizers или Thread.Sleep(скажем, 1000), то всегда будет выводиться success.
Есть подозрение что не всегда. Учитывая что вы работу GC почему-то смотрите на неуправляемом ресурсе, который по определению нельзя оставлять на милость GC (почему так, см. выше) -- я бы экспериментировал с объектом + десктруктор, который меняет какое-то глобальное значение. Типа так:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var neko = new Neko();
neko = null;
Console.WriteLine($"{GC.GetTotalAllocatedBytes(true)}");
GC.Collect();
Console.WriteLine($"{Neko.Flag}");
Console.WriteLine($"{GC.GetTotalAllocatedBytes(true)}");
 
class Neko
{
    public static int Flag = 0;
 
    private int[] buff = new int[1024];
 
    ~Neko() => Interlocked.Increment(ref Flag);
}
Ещё такой момент: по идеи "финализировать объект" не тоже самое что "высвободить память, что занимает программа". Тут лучше починайте подробней, ибо я над этим сильно не сидел не разбирался. Грубо говоря, возможна такая картина:
- вызываем GC
- он отрабатывает 100500 объектов, финализирует их и всё такое
- программа всё ещё занимает 100500 байт в ОЗУ.

По идеи CLR может решить, что "мне эта память ещё дальше пригодиться, так что я её пока отдавать не буду".
1
 Аватар для INexteR
17 / 16 / 1
Регистрация: 25.01.2023
Сообщений: 468
10.02.2024, 18:39  [ТС]
Цитата Сообщение от Wolfdp Посмотреть сообщение
всё же мне кажется что если в стеке если переменная, и на этом уровне есть вызов GC -- магии не случится
При компиляции сборки с ключом /debug компилятор применяет к полученной сборке атрибут DebuggableAttribute с установленным флагом DisableOptimizations. При компиляции метода во время выполнения JIT-компилятор видит, что атрибут задан, и искусственно продлевает время жизни переменных до конца метода. А если применяется оптимизация, как в моем случае, то сборщик видит, что после вызова Collect метод Main не использует переменную. Поэтому далее в приложении нет переменной, ссылающейся на объект FileStream, и сборщик мусора освобождает занятую им память.
Цитата Сообщение от Wolfdp Посмотреть сообщение
C#
1
fs = null;
Приравнивание локальной переменной к null равнозначно отсутствию ссылки на эту переменную. Поэтому JIT-компилятор в ходе оптимизации полностью уберет эту строку из программы.
Цитата Сообщение от Wolfdp Посмотреть сообщение
Ещё такой момент: по идеи "финализировать объект" не тоже самое что "высвободить память, что занимает программа
Методы Finalize вызываются при завершении сборки мусора для объектов, которые сборщик определил для утилизации. То есть такой объект переживет сборку мусора и будет переведен в старшее поколение. Такие объекты живут намного дольше. Для освобождения памяти, занятой объектами, требующими финализации, сборку мусора нужно выполнять дважды. На самом деле может понадобиться и больше операций сборки мусора, поскольку объекты переходят в следующее поколение.
Цитата Сообщение от Wolfdp Посмотреть сообщение
- программа всё ещё занимает 100500 байт в ОЗУ.
Скорее всего потому что некоторые объекты перешли в старшее поколение и оно ещё не превысило порога для дефрагментации кучи.
0
Эксперт .NET
 Аватар для Wolfdp
3790 / 1767 / 371
Регистрация: 15.06.2012
Сообщений: 6,543
Записей в блоге: 3
10.02.2024, 19:46
Детальней не подскажу.

Праздный вопрос -- вы с какой целью настолько подробно копаеет "во что комплилиться" и "когда вызывается GC и к чему это приводит"? Если к максимальной оптимизации кода прям на низком уровне -- ок, это скорее всего имеет смысл. Если чисто "чтобы знал, на практике пригодится"... ну, не уверен.
1
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
BasicMan
Эксперт
29316 / 5623 / 2384
Регистрация: 17.02.2009
Сообщений: 30,364
Блог
10.02.2024, 19:46

Сборщик мусора не удаляет объект
Сборщик мусора не удаляет объект. Мне необходимо чтобы GC его удалил, но этого не происходит. Привожу предельно упрощенный код моей...

Передача файлового потока в функцию
Здрасти. ifstream in("1.txt"); что возвращает in? как передать этот поток (in) в функцию которая выводит символы? void...

PyWin - чтение файлового потока
Здравствуйте! Проблема в строке с функцией ReadFile - TypeError: The object is not a PyHANDLE object import win32api import...

Выполнение операций с числами из файлового потока
Здравствуйте, помогите составить программу. Задание выглядит так: Написать программу которая берет значения для переменных x,y,z,q из...

Переключение файлового потока ввода вывода
Есть прога. Если закоментить первый цикл то будет читать из файла, если второй то будет его писать. Теперь вопрос: как её заставить делать...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
32
Ответ Создать тему
Новые блоги и статьи
Запрет дублирования строк в табличной части
Maks 13.09.2026
Реализация из решения ниже выполнена на нетиповом справочнике "Нормы ТО" с табличной часть "Виды ТО", разработанного в КА2, со следующими реквизитами: - ВидТО (СправочникСсылка. ВидыТО); - ВидГСМ. . .
Скрипты Tampermonkey для CyberForum, ChatGPT, Claude и пр.
Jin X 06.09.2026
Скрипты Tampermonkey для CyberForum, ChatGPT, Claude и пр. Работая с форумом и нейросетями в браузере часто хочется что-то подкорректировать или добавить какого-то функционала. Ниже прикреплён. . .
Программа опроса у.з. расходомера SLS-720F
Argus19 02.09.2026
Программа опроса у. з. расходомера SLS-720F Программа опрашивает один раз в минуту три ультразвуковых расходомера SLS-720F через интерфейс RS-485 по протоколу Modbus RTU. Опрашиваются регистры. . .
Hyper-V: Компьютер должен поддерживать доверенный платформенный модуль 2.0.
Maks 31.08.2026
При установке Windows 11 на виртуальную машину Hyper-V 2-го поколения вылезла такая ошибка: Решение: в параметрах виртуальной машины, в разделе "Безопасность" (Security) активировать флаг. . .
Архитектура биовида Стива в Майнкрафте: Зачем бонобо кубический каннибализм
anaschu 30.08.2026
Кубический Вагинокапитализм в Minecraft: Математический инвариант ОДУ и рок Стивов-бонобо Главная задача разработанной «Модели Всего» — наглядно продемонстрировать наличие системной «судьбы». . .
Оттачиваю умение писать js программы.
russiannick 30.08.2026
Проектом выходного дня стало написание Книги шифров Виженера. Итогом стала версия 200, синий туман. Синий туман назван так, потому что замораживает текст под собой. Нажатие синих кнопок управляют. . .
мат медиц модель 30. презентация проекта
anaschu 27.08.2026
хоп хоп хоп хидахоп, а я кладую))
Как у меня протекала болезнь
zorxor 27.08.2026
Здравствуйте, друзья! Эта запись блога предназначена именно для вас - для моих дорогих друзей, которые знали меня лично. Чтобы ответить на вопрос - а что же со мной произошло на самом деле? Я учился. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru