Порядок закрытия FileStream в Dispose
От: Fortnum  
Дата: 15.09.14 04:18
Оценка:
Предположим, есть некий класс A, у которого 2 поля типа FileStream, каким-то образом инициализируются. Если для внутреннего употребления надо сделать так, чтобы эти FileStream'ы в определенном порядке деинициализировались, я правильно понимаю, что шаблон Dispose будет ошибочным вариантом, т.к. в момент исполнения A.Dispose эти FileStream'ы уже могут быть закрыты GC?

Как лучше сделать для своего внутреннего употребления?

Если в момент инициализации эти FileStream'ы помещать в какой-то статический список-поле A.List<FileStream>, а в A.Dispose после деинициализации в нужном порядке, убирать их оттуда, это хорошее решение для внутреннего употребления?

public class A : IDisposable
{
    public A() { ... }

    public FileStream FileStream1;
    public FileStream FileStream2;

    ~A()
    {
        Dispose();
    }

    public void Dispose()
    {
        File.Delete(FileStream1);

        FileStream1.Dispose();

        FileStream2.Dispose();
    }
}
Re: Порядок закрытия FileStream в Dispose
От: TK Лес кывт.рф
Дата: 15.09.14 04:46
Оценка:
Здравствуйте, Fortnum, Вы писали:

F>Предположим, есть некий класс A, у которого 2 поля типа FileStream, каким-то образом инициализируются. Если для внутреннего употребления надо сделать так, чтобы эти FileStream'ы в определенном порядке деинициализировались, я правильно понимаю, что шаблон Dispose будет ошибочным вариантом, т.к. в момент исполнения A.Dispose эти FileStream'ы уже могут быть закрыты GC?


Вы путаете Dispose и Finalize. GC знает только про Finalize. Шаблон Dispose — это реализация IDisposable и использование в нем финализатора в большинстве случаев неправильно.

F>Как лучше сделать для своего внутреннего употребления?


Для внутреннего применения лучше следить за созданными A и своевременно вызывать Dispose.
Если будет спокойнее то и A.List<FileStream> тоже мало чего гарантирует
Если у Вас нет паранойи, то это еще не значит, что они за Вами не следят.
Re[2]: Порядок закрытия FileStream в Dispose
От: Fortnum  
Дата: 15.09.14 04:58
Оценка:
Здравствуйте, TK, Вы писали:

F>>Предположим, есть некий класс A, у которого 2 поля типа FileStream, каким-то образом инициализируются. Если для внутреннего употребления надо сделать так, чтобы эти FileStream'ы в определенном порядке деинициализировались, я правильно понимаю, что шаблон Dispose будет ошибочным вариантом, т.к. в момент исполнения A.Dispose эти FileStream'ы уже могут быть закрыты GC?

TK>Вы путаете Dispose и Finalize. GC знает только про Finalize. Шаблон Dispose — это реализация IDisposable и использование в нем финализатора в большинстве случаев неправильно.

Это понятно, что GC не знает про Dispose. Но почему вызов Dispose из финализатора — это неправильно?

F>>Как лучше сделать для своего внутреннего употребления?

TK>Для внутреннего применения лучше следить за созданными A и своевременно вызывать Dispose.

Так ведь фишка финализатора в том, что можно не следить, разве не так?

TK>Если будет спокойнее то и A.List<FileStream> тоже мало чего гарантирует


А вот это почему? Статический же.
Re: Порядок закрытия FileStream в Dispose
От: Sinix  
Дата: 15.09.14 05:40
Оценка:
Здравствуйте, Fortnum, Вы писали:

F>Предположим, есть некий класс A, у которого 2 поля типа FileStream, каким-то образом инициализируются. Если для внутреннего употребления надо сделать так, чтобы эти FileStream'ы в определенном порядке деинициализировались, я правильно понимаю, что шаблон Dispose будет ошибочным вариантом, т.к. в момент исполнения A.Dispose эти FileStream'ы уже могут быть закрыты GC?


0. Dispose pattern всё-таки надо писать правильно. Вот образец, а вот подробнейшее описание что, куда и зачем.

1. Если вы можете гарантировать, что весь код, использующий A, не забудет вызвать A.Dispose() — решение вполне рабочее.
В момент вызова IDisposable.Dispose() потоки будут живы (если вы не закрыли их ранее).

2. А вот с реализацией ~A() у вас косяки.
* Во-первых, ваш Dispose() будет вызван несколько раз: один раз из IDisposable.Dispose(), второй — из финализатора. Это лечится GC.SuppressFinalize().
* Во-вторых, IDisposable.Dispose() может быть вызван повторно пользовательским кодом (например, чтобы освободить ресурс чуть пораньше). Это абсолютно нормальная практика, в Dispose лучше вставить проверку на повторный вызов (см ссылку на образец выше)
* В-третьих,
    File.Delete(FileStream1);
    FileStream1.Dispose();

скорее всего бросит IOException. Порядок надо поменять.

* В четвёртых, порядок вызова финализаторов ничем не гарантируется (про CriticalFinalizerObject пока забыли). В момент вызова ~A() любой из потоков уже может быть закрыт.

В общем, ~A вас никак не спасёт, зато .Dispose() должно хватить.


F>Если в момент инициализации эти FileStream'ы помещать в какой-то статический список-поле A.List<FileStream>, а в A.Dispose после деинициализации в нужном порядке, убирать их оттуда, это хорошее решение для внутреннего употребления?

Если не забудете про блокировки и не пугают возможные утечки ресурсов, то, в принципе, решение рабочее. Но ничем не лучше варианта с Dispose().

Как всегда, опишите задачу, которую пытаетесь решить. Все симптомы, что из "простой код, работает в 99% случаев" и "монстрокод, работает в 99.1% случаев" у вас вырисовывается 99.1
Re[2]: Порядок закрытия FileStream в Dispose
От: Fortnum  
Дата: 15.09.14 06:28
Оценка:
Здравствуйте, Sinix, Вы писали:

F>>Предположим, есть некий класс A, у которого 2 поля типа FileStream, каким-то образом инициализируются. Если для внутреннего употребления надо сделать так, чтобы эти FileStream'ы в определенном порядке деинициализировались, я правильно понимаю, что шаблон Dispose будет ошибочным вариантом, т.к. в момент исполнения A.Dispose эти FileStream'ы уже могут быть закрыты GC?


S>1. Если вы можете гарантировать, что весь код, использующий A, не забудет вызвать A.Dispose() — решение вполне рабочее.

S>В момент вызова IDisposable.Dispose() потоки будут живы (если вы не закрыли их ранее).

Да, вот это "если вы не закрыли их ранее" — один из косяков. Более того, кто-то может попробовать работать с этими потоками после вызова A.Dispose Но это для внутреннего употребления, поэтому чисто джентльменское соглашение... хотя...

S>2. А вот с реализацией ~A() у вас косяки.

S>* Во-первых, ваш Dispose() будет вызван несколько раз: один раз из IDisposable.Dispose(), второй — из финализатора. Это лечится GC.SuppressFinalize().
S>* Во-вторых, IDisposable.Dispose() может быть вызван повторно пользовательским кодом (например, чтобы освободить ресурс чуть пораньше). Это абсолютно нормальная практика, в Dispose лучше вставить проверку на повторный вызов (см ссылку на образец выше)

Спасибо. Это я всё в курсе, это всё будет учтено. Хотел конкретно вопрос порядка осветить.

S>* В-третьих,

S>
S>    File.Delete(FileStream1);
S>    FileStream1.Dispose();
S>

S>скорее всего бросит IOException. Порядок надо поменять.

Не должен бросить, т.к. FileStream1 открыт с FileShare.Delete. Вообще, тут есть вопрос... сейчас задам его отдельной веткой.

S>Как всегда, опишите задачу, которую пытаетесь решить. Все симптомы, что из "простой код, работает в 99% случаев" и "монстрокод, работает в 99.1% случаев" у вас вырисовывается 99.1


Задача написать библиотечку, позволяющую создавать и закрывать FileStream'ы в определенном порядке Наверное, можно задачу описать так... свой FileStream, который создает не один файл в ОС, а два. При этом инициализирует эти два файла в определенном порядке.
Re[3]: Порядок закрытия FileStream в Dispose
От: Sinix  
Дата: 15.09.14 10:12
Оценка: +1
Здравствуйте, Fortnum, Вы писали:

F>Задача написать библиотечку, позволяющую создавать и закрывать FileStream'ы в определенном порядке Наверное, можно задачу описать так... свой FileStream, который создает не один файл в ОС, а два. При этом инициализирует эти два файла в определенном порядке.

Это не задача, это ваше решение, только описанное другими словами

Смотрите в чём проблема (утрирую):

1. Вопрос на форуме: "как распечатать кнопку 1px * 1px заданного цвета?"

2. ... три страницы пропускаем ...

3. Уточняем вопрос: "как сделать любой контрол, обрабатывающий нажатия, и позволяющий задать себе цвет, как распечатать результат"?

4. ... ещё пару страниц !@#$%^&*() !?? ...

5. Собственно вопрос: "как сделать попиксельный редактор для иконок?"


На "неправильные" вопросы, как в п.1., когда человек уже уткнулся в тупик неправильно выбранных решений, но назад повернуть ещё не хочет, правильного ответа нет. Можно только заставить человека откатить рассуждения на несколько шагов назад и помочь найти ошибку в цепочке выбора. Это очень частый случай, вот
Автор: Sinix
Дата: 18.08.11
и вот
Автор: Sinix
Дата: 18.12.12
примеры.

Почему мне кажется, что у вас именно такая ситуация?

Потому что нет (ну ок, я не могу придумать) ни одного сценария, когда "определённый порядок инициализации" решается именно порядком закрытия FileStream.
С точки зрения ФС — фиолетово, правим даты создания-изменения так, как надо.
Если речь про работу с девайсами через CreateFile -> FileStream, как вот тут — снова нужны детали. Возможно, всё решается через наследника SafeHandle.
Если речь не о FileStream, а о другом потоке — ничего сказать нельзя без знания, какие гарантии предоставляет Flush()/Close() этого потока.

Самый худший вариант: другой софт мониторит эти файлы, и освободить файл 2 нужно только после освобождения файла 1. Тут всё зависит от кривости этого софта и от последствий "ошибочного" освобождения файла два раньше файла 1.
Если ничего страшного типа потери данных не ожидается — хватит и .Dispose()


Короче, нет самой задачи — нет "правильных" ответов, сорри.
Re[4]: Порядок закрытия FileStream в Dispose
От: Fortnum  
Дата: 15.09.14 10:22
Оценка:
Здравствуйте, Sinix, Вы писали:

S>Самый худший вариант: другой софт мониторит эти файлы, и освободить файл 2 нужно только после освобождения файла 1. Тут всё зависит от кривости этого софта и от последствий "ошибочного" освобождения файла два раньше файла 1.

S>Если ничего страшного типа потери данных не ожидается — хватит и .Dispose()

Вот-вот. Этот вариант. Потеря данных ожидается, последовательность открытия эту проблему и решает. Только почему он самый худший?
Re[5]: Порядок закрытия FileStream в Dispose
От: Sinix  
Дата: 15.09.14 11:44
Оценка:
Здравствуйте, Fortnum, Вы писали:


S>>Самый худший вариант: другой софт мониторит эти файлы...

F>Вот-вот. Этот вариант. Потеря данных ожидается, последовательность открытия эту проблему и решает. Только почему он самый худший?

Потому что очень легко словить грабли из-за блокирования файла антивирусом, глюка ReadDirectoryChangesW типа такого (если используется для ожидания) или загадить производительность слишком частыми обращениями к файлу (особенно забавно, когда файл большой и superfetch начинает его тащить в память).

Самое прелестное, если код проверки выглядит как-то так :
    public static bool IsFileReady(String sFilename)
    {
        try
        {
            using (FileStream inputStream = File.Open(sFilename, FileMode.Open, FileAccess.Read, FileShare.None))
            {
                if (inputStream.Length > 0)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
        }
        catch (Exception)
        {
            return false;
        }
    }


"if (IsFileReady(file)) OpenFile(file);", ага.

Чем автору всей этой схемы обычный мютекс/семафор не угодил?
Re[6]: Порядок закрытия FileStream в Dispose
От: Fortnum  
Дата: 15.09.14 12:08
Оценка:
Здравствуйте, Sinix, Вы писали:

S>Потому что очень легко словить грабли из-за блокирования файла антивирусом


А как антивирус блокирует? Может ли он блокировать уже заблокированный моим процессом файл? Если он просто блокирует как обычный сторонний процесс (без соблюдения порядка), то проблем нет. Главное, чтобы этот сторонний процесс ничего в само содержимое файла без взятия нужных блокировок не писал. А читать — без проблем.

Есть еще в тему вопросы по снятию бэкапа в режиме реального времени

S>из-за глюка ReadDirectoryChangesW типа такого (если используется для ожидания)


Эта штука без гарантии может терять уведомления, поэтому не используется.

S>"if (IsFileReady(file)) OpenFile(file);", ага.


Не, такого нет 100%

S>Чем автору всей этой схемы обычный мютекс/семафор не угодил?


Да почему не угодил? Угодил. А между машинами как?
Re[7]: Порядок закрытия FileStream в Dispose
От: Sinix  
Дата: 15.09.14 12:41
Оценка: 9 (1)
Здравствуйте, Fortnum, Вы писали:

F>А как антивирус блокирует? Может ли он блокировать уже заблокированный моим процессом файл?

Нет с вероятностью 0.99. Точнее, фильтр фс антивируса срабатывает или в процессе, или до открытия файла (собственно, для прикладного кода оно выглядит одинаково). Вариант с "антивирус разбушевался и позакрывал все открытые хендлы" ничем принципиально не отличается от "отвалилась сеть/раздел", только намного менее вероятен. Отдельно от него защищаться нафиг не надо.

F>Если он просто блокирует как обычный сторонний процесс (без соблюдения порядка), то проблем нет.

Тогда ок. Мне попадались варианты реализации, которые падали уже на такой мелочи

F>Есть еще в тему вопросы по снятию бэкапа в режиме реального времени

Оххх... http://alphavss.codeplex.com/ разве что.

S>>из-за глюка ReadDirectoryChangesW типа такого (если используется для ожидания)


F>Эта штука без гарантии может терять уведомления, поэтому не используется.

Если бы только это... Там всё — от ложных срабатываний и до зависаний при слишком активной работой с директорией. Самый непредсказуемый компонент в System.IO по-моему.


S>>Чем автору всей этой схемы обычный мютекс/семафор не угодил?

F>Да почему не угодил? Угодил. А между машинами как?
Только какая-нибудь из разновидностей message queue (т.е. много писателей — один выгребающий из очереди), всё остальное, увы, работает очень своеобразно.
Про похожий баг с SMB рассказывали пару месяцев назад, с шарами на NFS, если ничего не забыл, тоже были какие-то проблемы.

На win server с 2003 по 2012 похожих граблей не наблюдал, но это не значит, что их гарантированно не будет. Короче говоря, всё тлен
Re[3]: Порядок закрытия FileStream в Dispose
От: TK Лес кывт.рф
Дата: 15.09.14 16:22
Оценка:
Здравствуйте, Fortnum, Вы писали:

F>Это понятно, что GC не знает про Dispose. Но почему вызов Dispose из финализатора — это неправильно?


Посмотрите в MSDN "каноническую" реализацию Disposable паттерна — в финализаторе надо освобождать только unmanaged ресурсы. А FileStream вполне себе managed.

F>Так ведь фишка финализатора в том, что можно не следить, разве не так?


Фишка финализатора в том, что если не уследить то, оно может быть и уберет, потом, если время дойдет.

TK>>Если будет спокойнее то и A.List<FileStream> тоже мало чего гарантирует

F>А вот это почему? Статический же.

Статический это не значит, что и до него очередь не дойдет.
Если у Вас нет паранойи, то это еще не значит, что они за Вами не следят.
Re[4]: Порядок закрытия FileStream в Dispose
От: Fortnum  
Дата: 15.09.14 16:43
Оценка:
Здравствуйте, TK, Вы писали:

F>>Это понятно, что GC не знает про Dispose. Но почему вызов Dispose из финализатора — это неправильно?

TK>Посмотрите в MSDN "каноническую" реализацию Disposable паттерна — в финализаторе надо освобождать только unmanaged ресурсы. А FileStream вполне себе managed.

Да вроде нет запрета явного на проведение действий с managed-ресурсами в финализаторе. Надо просто иметь в виду, что это другой поток, и нюансы с тем, что порядок выполнения финализаторов не определен. По мне так это как раз тот случай, когда можно и поиметь в виду С другой стороны, нюанс в том, что это будет весьма неожидано.

Кстати, по ходу вопрос, вроде раньше было так, что все остальные потоки приостанавливались на время сбора мусора, который проводился в одном своём потоке, но это поведение в будущем не гарантировалось. Я ничего не путаю? Так и есть до сих пор?

F>>Так ведь фишка финализатора в том, что можно не следить, разве не так?

TK>Фишка финализатора в том, что если не уследить то, оно может быть и уберет, потом, если время дойдет.

Вообще, конечно, нафига козе баян — что в лоб, что по лбу, но при выборе из 2-х вариантов: "поздно" и "никогда", даже не знаю что выбрать Кроме того, в using { } красиво смотрится Но количество нюансов все же подсказывает, что лучше так не делать, да?

TK>>>Если будет спокойнее то и A.List<FileStream> тоже мало чего гарантирует

F>>А вот это почему? Статический же.
TK>Статический это не значит, что и до него очередь не дойдет.

Я в замешательстве. Как до него может дойти, если он статический, а домен приложения один, выгрузки не предполагается?
Re[6]: Порядок закрытия FileStream в Dispose
От: Sharov Россия  
Дата: 15.09.14 16:47
Оценка:
Здравствуйте, Sinix, Вы писали:

S>"if (IsFileReady(file)) OpenFile(file);", ага.


S>Чем автору всей этой схемы обычный мютекс/семафор не угодил?


А что не так в приведенном коде?
Кодом людям нужно помогать!
Re[7]: Порядок закрытия FileStream в Dispose
От: Fortnum  
Дата: 15.09.14 16:56
Оценка:
Здравствуйте, Sharov, Вы писали:

S>>"if (IsFileReady(file)) OpenFile(file);", ага.

S>>Чем автору всей этой схемы обычный мютекс/семафор не угодил?
S>А что не так в приведенном коде?

Например, если в OpenFile пойдет открытие так же, как и в IsFileReady с FileShare.None, то между возвратом из IsFileReady и открытием этого файла в OpenFile, файл может быть открыт другим процессом, и открытие файла в OpenFile вызовет IOException. Смысл в IsFileReady теряется, а если исключение в/из OpenFile не будет поймано, то приведет к сбоям программы. Самое страшное, что в каком-то % случаев оно будет работать успешно, в некоторых случаях до 99.999%
Re[8]: Порядок закрытия FileStream в Dispose
От: Sharov Россия  
Дата: 15.09.14 17:19
Оценка:
Здравствуйте, Fortnum, Вы писали:

F>Здравствуйте, Sharov, Вы писали:


S>>>"if (IsFileReady(file)) OpenFile(file);", ага.

S>>>Чем автору всей этой схемы обычный мютекс/семафор не угодил?
S>>А что не так в приведенном коде?

F>Например, если в OpenFile пойдет открытие так же, как и в IsFileReady с FileShare.None, то между возвратом из IsFileReady и открытием этого файла в OpenFile, файл может быть открыт другим процессом, и открытие файла в OpenFile вызовет IOException. Смысл в IsFileReady теряется, а если исключение в/из OpenFile не будет поймано, то приведет к сбоям программы. Самое страшное, что в каком-то % случаев оно будет работать успешно, в некоторых случаях до 99.999%


Ну так это известный факт, и тут ничего не поделаешь. Самое главное ловить IOException.
Кодом людям нужно помогать!
Re[9]: Порядок закрытия FileStream в Dispose
От: Fortnum  
Дата: 15.09.14 17:29
Оценка: 3 (1) +1
Здравствуйте, Sharov, Вы писали:

S>>>>"if (IsFileReady(file)) OpenFile(file);", ага.

S>>>>Чем автору всей этой схемы обычный мютекс/семафор не угодил?
S>>>А что не так в приведенном коде?
F>>Например, если в OpenFile пойдет открытие так же, как и в IsFileReady с FileShare.None, то между возвратом из IsFileReady и открытием этого файла в OpenFile, файл может быть открыт другим процессом, и открытие файла в OpenFile вызовет IOException. Смысл в IsFileReady теряется, а если исключение в/из OpenFile не будет поймано, то приведет к сбоям программы. Самое страшное, что в каком-то % случаев оно будет работать успешно, в некоторых случаях до 99.999%
S>Ну так это известный факт, и тут ничего не поделаешь. Самое главное ловить IOException.

Лучше, чтоб IsFileReady возвращал FileStream, если FileReady.
Re[5]: Порядок закрытия FileStream в Dispose
От: TK Лес кывт.рф
Дата: 15.09.14 20:15
Оценка: 9 (1)
Здравствуйте, Fortnum, Вы писали:

F>Я в замешательстве. Как до него может дойти, если он статический, а домен приложения один, выгрузки не предполагается?


Приложение рано или поздно завершится. А перед этим придут и финализаторы.
Если у Вас нет паранойи, то это еще не значит, что они за Вами не следят.
Re[6]: Порядок закрытия FileStream в Dispose
От: Fortnum  
Дата: 16.09.14 03:29
Оценка:
Здравствуйте, TK, Вы писали:

F>>Я в замешательстве. Как до него может дойти, если он статический, а домен приложения один, выгрузки не предполагается?

TK>Приложение рано или поздно завершится. А перед этим придут и финализаторы.

А вот не выгружается что-то Берут сомнения, а приходят ли финализаторы?

static void Main()
{
    Console.WriteLine("Press ANY key to unload current application domain and exit the application.");

    AppDomain.CurrentDomain.DomainUnload += (obj, e) =>
    {
        Console.WriteLine("Press ANY key to really unload the application domain.");
        Console.ReadKey();
    };

    Console.ReadKey();
}
Re[7]: Порядок закрытия FileStream в Dispose
От: Fortnum  
Дата: 16.09.14 03:34
Оценка:
Здравствуйте, Fortnum, Вы писали:

F>Здравствуйте, TK, Вы писали:


F>>>Я в замешательстве. Как до него может дойти, если он статический, а домен приложения один, выгрузки не предполагается?

TK>>Приложение рано или поздно завершится. А перед этим придут и финализаторы.

F>А вот не выгружается что-то Берут сомнения, а приходят ли финализаторы?


Да, все-таки они существуют!

class AreThereFinalizersForStatic
{
    ~AreThereFinalizersForStatic()
    {
        Console.WriteLine("YES!");
        Console.ReadKey();
    }
}

class Program
{
    static AreThereFinalizersForStatic _areThereFinalizersForStatic = new AreThereFinalizersForStatic();

    static void Main()
    {
        Console.WriteLine("Press ANY key to unload current application domain and exit the application.");

        AppDomain.CurrentDomain.DomainUnload += (obj, e) =>
        {
            Console.WriteLine("Press ANY key to really unload the application domain.");
            Console.ReadKey();
        };

        Console.ReadKey();
    }
}
Re[7]: Порядок закрытия FileStream в Dispose
От: AndrewVK Россия http://blogs.rsdn.org/avk
Дата: 16.09.14 03:40
Оценка: 3 (1) +1
Здравствуйте, Fortnum, Вы писали:

F>А вот не выгружается что-то Берут сомнения, а приходят ли финализаторы?


http://msdn.microsoft.com/EN-US/library/9tcc8a46(v=VS.110,d=hv.2).aspx
... << RSDN@Home 1.0.0 alpha 5 rev. 0 on Windows 8 6.2.9200.0>>
AVK Blog
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.