2. вызовы к GetNext() и Dispose() асинхронные, но внутри встают в очередь. Сделал так, что GetNext(), начавшиеся до Dispose, результат вернут, а начавшиеся после -- нет.
На сколько 1 и 2 "правильно" и соответствует ожиданиям от IDisposable?
Ok
Y>1. после вызова Dispose() метод GetNext() говорит, что возвращать нечего (вместо того, чтобы кидать эксепшн https://msdn.microsoft.com/en-us/library/system.objectdisposedexception(v=vs.110).aspx)
Y>2. вызовы к GetNext() и Dispose() асинхронные, но внутри встают в очередь. Сделал так, что GetNext(), начавшиеся до Dispose, результат вернут, а начавшиеся после -- нет.
Y>На сколько 1 и 2 "правильно" и соответствует ожиданиям от IDisposable?
Согласно Framework Design Guildelines, а значит и согласно принципу наименьшего удивления большинства .NET разработчиков, это поведение не будет соответствовать ожиданиям от использования IDisposable. Вышеуказанный букварь (и вот эта статья) четко устанавливают ожидания от поведения объекта после вызова метода Dispose: метод этот синхронный и последующий вызов любого другого экземплярного метода должен падать с ObjectDisposeException (двойнов вызов метода Dispose допустим, исключения быть не должно)..
Навскидку сложно сказать, что имеется ввиду под пунктом 2, особенно не имя сигнатуры метода GetNext (какой он такой асинхронный? Возвращает Task<Something>?).
Я бы посоветовал просто сделать метод Dispose обычным (синхронным) и уйти от этой проблемы. Т.е. вызов блокируется до тех пор, пока все текущие запросы, поставленные в очередь не будут обработаны.
Под "вызовы ... асинхронные" имел в виду, что начало нового вызова может случать до окончания предыдущего.
bool GetNext(out SomeType result)
ST>Т.е. вызов блокируется до тех пор, пока все текущие запросы, поставленные в очередь не будут обработаны.
Почти так и сделано.
На сколько критично блокировать вызов Dispose? Может быть просто "запланировать" вызов освобождения внутренних ресурсов и сделать это по завершении последнего поставленного в очередь GetNext()? Dispose при этом "отпустить".
Изначально там еще был метод, типа, Stop(), после которого GetNext начинал возвращать false, но его же надо было специально вызывать а во всех двух клиентах это совпадало с вызовом Dispose().
Верну, как было.
Спасибо!
ST> Согласно Framework Design Guildelines, ... это поведение не будет соответствовать ожиданиям от использования IDisposable. https://msdn.microsoft.com/en-us/library/b1yfkh5e(v=vs.110).aspx
√ DO throw an ObjectDisposedException from any member that cannot be used after the object has been disposed of.
Или я не понял, что именно "не будет соответствовать ожиданиям", или вроде бы все в порядке: после Dispose не при любом обращении к объекту надо кидать эксепшн. Цепляюсь к формулировкам игнорируя суть?
Я конечно всё понимаю, но этого я не понимаю.
Ссылаться на FDG и рекомендовать то, чего там нет — это уже перебор
ST>Согласно Framework Design Guildelines ... метод этот синхронный и последующий вызов любого другого экземплярного метода должен падать с ObjectDisposeException (двойнов вызов метода Dispose допустим, исключения быть не должно).
В гадлайнах кое-что другое написано:
DO throw an ObjectDisposedException from any member that cannot be used after the object has been disposed of.
By convention, this method is used for all tasks associated with freeing resources held by an object, or preparing an object for reuse.
Пример использования Disposable для асинхронных операций — см Rx. Например, OnNext() вот тут.
Пример объектов, которые великолепно переиспользуются после Dispose — объекты с open-close поведением, типа SqlConnection / наследников TraceListener. В них Dispose() == Close(). Последующий Open отрабатывает без проблем.
А мне вот сначала даже в голову не пришло не поверить на слово чуваку из Рэдмонда
S>объекты с open-close поведением,
У меня, правда, "семантически" все чуть по-другому: после close() из объекта можно пытаться что-то получить, но безрезультатно.
При этом поведение "пустого" объекта не будет отличаться от поведения диспоузнотого в процессе опустошения.
Т.е. Dispose == Close, но такой Close, что его последствия видно снаружи и эта видимость используется в логике.
В общем есть (условно) три сценария для Dispose():
1. Scope. Всякие транзакции, потоки, unmanaged-ресурсы. В общем, любой объект, чей нормальный жизненный цикл завершается вызовом Dispose. Вот тут ObjectDisposedException в самый раз. UPD. Как правило, исключение кидают только методы, содержащие поведение. Всякие геттеры/получение результата etc работают нормально.
2. Reusable objects. Встречается гораздо реже, но всё-таки встречается. Dispose() эквивалентен вызову Close(), последующий Open() переводит объект в рабочее состояние.
3. LSD. Реализации, которые не следуют гадлайнам. Например, в Task.cs:
if ((Options & (TaskCreationOptions)InternalTaskOptions.DoNotDispose) != 0)
{
return;
}
if (!IsCompleted)
{
throw new InvalidOperationException(Environment.GetResourceString("Task_Dispose_NotCompleted"));
}
// ...
Но лучший сборник примеров — это безусловно Rx. Там есть всё.
Ещё есть четвёртый вариант — т.н. toggle disposables. Это простые disposable взамен пары методов BeginSmth()/EndSmth(), т.е.
grid.BeginInit();
// ...
grid.EndInit();
// vsusing (grid.InitScope()) // NB: no var used
{
// ...
}
По сути, это те же disposable из п.1, только у них нет никаких публичных мемберов и ObjectDisposedException кидать нечем. Нет ручек — нет конфетки
Выбирайте, что вам больше подходит, используйте на здоровье.
Здравствуйте, SergeyT., Вы писали:
ST>Согласно Framework Design Guildelines, а значит и согласно принципу наименьшего удивления большинства .NET разработчиков, это поведение не будет соответствовать ожиданиям от использования IDisposable. Вышеуказанный букварь (и вот эта статья) четко устанавливают ожидания от поведения объекта после вызова метода Dispose: метод этот синхронный и последующий вызов любого другого экземплярного метода должен падать с ObjectDisposeException (двойнов вызов метода Dispose допустим, исключения быть не должно)..
Я такое только в книжках видел и кое где сам реализовывал. Как то так
Здравствуйте, Sinix, Вы писали:
S>Я конечно всё понимаю, но этого я не понимаю. S>Ссылаться на FDG и рекомендовать то, чего там нет — это уже перебор
А можно подробнее? А то критиковать что-то, чего автор не имел ввиду — это уже перебор
ST>>Согласно Framework Design Guildelines ... метод этот синхронный и последующий вызов любого другого экземплярного метода должен падать с ObjectDisposeException (двойнов вызов метода Dispose допустим, исключения быть не должно).
S>В гадлайнах кое-что другое написано: S>
S>DO throw an ObjectDisposedException from any member that cannot be used after the object has been disposed of.
S>Выделенный текст важен.
Я подхожу к этому со стороны ООП (сори). И метод Dispose логически разрушает инвариант созданного объекта. Наличие этого метода говорит о том, что инвариант (чем бы он не был, ресурсом, логическим или физическим состоянием и т.п.) инициализируется в конструкторе, но разрушается не при недоступности объекта (и последующем пристреливании объекта с помощью GC). Исходя из вопроса топикстартера не совсем ясно, чем этот инвариант является, но, мое предположение было таким, что последующий вызов метода GetNext меняет поведение после вызова Dispose и я рассматривал этот случай, как тот, что попадает под выделенный важный текст.
Вообще, выделенный важный текст довольно стремный. Почему? Да потому что он раскрывает детали реализации. Для меня — простого клиента класса, любое обращение к любому члену потенциально означает, что я дергаю "member that cannot be used after the object has been disposed". Это же детали реализации, скрытые от меня. Верно? Именно поэтому я придерживаюсь более простого правила, которое противоречит выделенному важному тексту, а именно: любой член Disposable класса (кроме метода Dispose) должен бросать исключение при обращении к нему после вызова Dispose. И это даже может быть характерно всяким property getter-ам, поскольку в один момент их реализация может измениться и вместо обращения к свойству я начну обращаться к внутреннему состоянию, которое уже разрушено.
S>Ну и см. описание самого метода: S>
S> By convention, this method is used for all tasks associated with freeing resources held by an object, or preparing an object for reuse.
S>Пример использования Disposable для асинхронных операций — см Rx. Например, OnNext() вот тут.
Я не говорил об использовании Dispose для асинхронных операций, я писал о вреде "асинхронной" реализации метода Dispose, которая ставит что-то в очередь. И вот это, ИМХО, очень и очень плохо, поскольку нарушает принцип наименьшего удивления, согласно которому состояние объекта изменяется сразу же после вызова метода, а не когда-то позже.
Полностью поддерживаю все вышесказанное и не поддерживаю гайдлайны в той строчке.
ST>состояние объекта изменяется сразу же после вызова метода, а не когда-то позже.
Тут можно "выкрутиться" такими способами:
1. состояние объекта меняется сразу после _окончания_ вызова, т.е. при вызове Dispose ставим разрушение в очередь после уже запланированных GetNext().
2. считаем, что время относительно (для разных потоков, пока не сделали какую-нибудь синхронизацию нет понятия "одновременно"), и тогда при (1) не нужно даже блокировать вызов Dispose. Поток, который вызвал Dispose, не сможет различить ситуации изменилось ли состояние объекта "сразу" или "когда-то позже" (заблокируется уже вызов GetNext и вернет false ну или кинет исключение по окончании своей работы).
А по гайдлайнам, кстати, после вызова Dispose вообще не положено узнавать состояние объекта.
Здравствуйте, SergeyT., Вы писали:
ST>А можно подробнее? А то критиковать что-то, чего автор не имел ввиду — это уже перебор
Там нечего подробнее рассказывать Если в рекомендации есть оговорка — ей не надо пренебрегать, иначе очередной Фаулер получается.
ST>Я подхожу к этому со стороны ООП (сори).
Ну вот в этом и проблема. FDG не про абстрактные идеи в вакууме, и даже не про рекомендации — они играют роль коанов в дзен-буддизме, не больше.
Основная идея книги — "научить разработчиков думать правильно". Т.е. для начала, мыслить не от решения, а от проблемы.
Как только мы принимаем любой довод за "должен соблюдаться всегда", начинаются проблемы. В нашем случае они начинаются, если объект продолжает жить после вызова Dispose(). Например, при асинхронных операциях — как у топикстартера.
Посмотри на observer в Rx, на Task, или на TraceListener. И попробуй применить к ним "должен бросать исключение" с учётом того факта, что любой из них используется из нескольких потоков
ST>Я не говорил об использовании Dispose для асинхронных операций, я писал о вреде "асинхронной" реализации метода Dispose, которая ставит что-то в очередь. И вот это, ИМХО, очень и очень плохо, поскольку нарушает принцип наименьшего удивления, согласно которому состояние объекта изменяется сразу же после вызова метода, а не когда-то позже.
А это вообще абсолютно не важно, потому что у нас и так есть "очередь" потоков и раньше или позже по факту произойдёт Dispose() по факту никакого значения не имеет. Разумеется, при условии что мы подстелили соломки с полем-флагом this.disposed.
Здравствуйте, ylem, Вы писали: Y>Есть самодельный класс, реализующий IDisposable и с одним методом GetNext, который или возвращает кое-что, или говорит, что нечего возвращать. Y>https://msdn.microsoft.com/en-us/library/system.idisposable(v=vs.110).aspx Y>Оказалось удобным для использования сделать: Y>1. после вызова Dispose() метод GetNext() говорит, что возвращать нечего (вместо того, чтобы кидать эксепшн https://msdn.microsoft.com/en-us/library/system.objectdisposedexception(v=vs.110).aspx) Y>2. вызовы к GetNext() и Dispose() асинхронные, но внутри встают в очередь. Сделал так, что GetNext(), начавшиеся до Dispose, результат вернут, а начавшиеся после -- нет. Y>На сколько 1 и 2 "правильно" и соответствует ожиданиям от IDisposable?
Тут уже написали — это неправильно. После Dispose можно вызвать только Dispose.
Для того что бы сделать правильный неблокирующий Dispose можно использовать счетчики. Но уже не ссылок, а внешних вызовов методов объекта.
Здравствуйте, Sinix, Вы писали:
S>[/cs] Но лучший сборник примеров — это безусловно Rx. Там есть всё.
Что-то у них на сайте контент поломался. Гугл и сам сайт ссылаются именно туда, значит дело в самом сайте. Вот pdf-версия книги introToRX на гугл-драйве, там эта глава есть (последние две страницы книги):
Здравствуйте, ylem, Вы писали:
Y>2. вызовы к GetNext() и Dispose() асинхронные, но внутри встают в очередь. Сделал так, что GetNext(), начавшиеся до Dispose, результат вернут, а начавшиеся после -- нет.
Y>На сколько 1 и 2 "правильно" и соответствует ожиданиям от IDisposable?
Так надо использовать не классы, а интерфейсы. Один IGetNext с методом GetNext, другой IDisposable с методом Dispose
Потребители IGetNext ничего про IDisposable не знают и за что им кидать ObjectDisposedException — непонятно.
А тем потребителям что знают про IDisposable ничего про IGetNext говорить не надо — так и вопросов "где мой ObjectDisposedException" никаких не возникнет.
Если у Вас нет паранойи, то это еще не значит, что они за Вами не следят.