Жизнь после Dispose (.net)
От: ylem  
Дата: 08.07.15 00:33
Оценка:
Есть самодельный класс, реализующий IDisposable и с одним методом GetNext, который или возвращает кое-что, или говорит, что нечего возвращать.
https://msdn.microsoft.com/en-us/library/system.idisposable(v=vs.110).aspx

Оказалось удобным для использования сделать:

1. после вызова Dispose() метод GetNext() говорит, что возвращать нечего (вместо того, чтобы кидать эксепшн https://msdn.microsoft.com/en-us/library/system.objectdisposedexception(v=vs.110).aspx)

2. вызовы к GetNext() и Dispose() асинхронные, но внутри встают в очередь. Сделал так, что GetNext(), начавшиеся до Dispose, результат вернут, а начавшиеся после -- нет.

На сколько 1 и 2 "правильно" и соответствует ожиданиям от IDisposable?
Re: Жизнь после Dispose (.net)
От: SergeyT. США http://sergeyteplyakov.blogspot.com/
Дата: 08.07.15 00:59
Оценка: +1
Здравствуйте, ylem, Вы писали:

Y>Есть самодельный класс, реализующий IDisposable и с одним методом GetNext, который или возвращает кое-что, или говорит, что нечего возвращать.

Y>https://msdn.microsoft.com/en-us/library/system.idisposable(v=vs.110).aspx

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 обычным (синхронным) и уйти от этой проблемы. Т.е. вызов блокируется до тех пор, пока все текущие запросы, поставленные в очередь не будут обработаны.
Re[2]: Жизнь после Dispose (.net)
От: ylem  
Дата: 08.07.15 01:27
Оценка:
Под "вызовы ... асинхронные" имел в виду, что начало нового вызова может случать до окончания предыдущего.
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 не при любом обращении к объекту надо кидать эксепшн. Цепляюсь к формулировкам игнорируя суть?
Отредактировано 08.07.2015 6:56 ylem . Предыдущая версия . Еще …
Отредактировано 08.07.2015 1:48 ylem . Предыдущая версия .
Re[2]: Жизнь после Dispose (.net)
От: Sinix  
Дата: 08.07.15 07:16
Оценка: 8 (3)
Здравствуйте, SergeyT., Вы писали:

Я конечно всё понимаю, но этого я не понимаю.
Ссылаться на 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 отрабатывает без проблем.
Re[3]: Жизнь после Dispose (.net)
От: ylem  
Дата: 08.07.15 07:36
Оценка:
S>Выделенный текст важен.

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

S>объекты с open-close поведением,


У меня, правда, "семантически" все чуть по-другому: после close() из объекта можно пытаться что-то получить, но безрезультатно.
При этом поведение "пустого" объекта не будет отличаться от поведения диспоузнотого в процессе опустошения.
Т.е. Dispose == Close, но такой Close, что его последствия видно снаружи и эта видимость используется в логике.
Отредактировано 08.07.2015 7:47 ylem . Предыдущая версия . Еще …
Отредактировано 08.07.2015 7:43 ylem . Предыдущая версия .
Re[4]: Жизнь после Dispose (.net)
От: Sinix  
Дата: 08.07.15 08:00
Оценка: 83 (4) :)
Здравствуйте, ylem, Вы писали:

А, да на вопрос забыл ответить


В общем есть (условно) три сценария для 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();

// vs
using (grid.InitScope()) // NB: no var used
{
  // ...
}
По сути, это те же disposable из п.1, только у них нет никаких публичных мемберов и ObjectDisposedException кидать нечем. Нет ручек — нет конфетки


Выбирайте, что вам больше подходит, используйте на здоровье.
Отредактировано 08.07.2015 8:02 Sinix . Предыдущая версия . Еще …
Отредактировано 08.07.2015 8:01 Sinix . Предыдущая версия .
Re[2]: Жизнь после Dispose (.net)
От: Ikemefula Беларусь http://blogs.rsdn.org/ikemefula
Дата: 08.07.15 16:02
Оценка:
Здравствуйте, SergeyT., Вы писали:

ST>Согласно Framework Design Guildelines, а значит и согласно принципу наименьшего удивления большинства .NET разработчиков, это поведение не будет соответствовать ожиданиям от использования IDisposable. Вышеуказанный букварь (и вот эта статья) четко устанавливают ожидания от поведения объекта после вызова метода Dispose: метод этот синхронный и последующий вызов любого другого экземплярного метода должен падать с ObjectDisposeException (двойнов вызов метода Dispose допустим, исключения быть не должно)..


Я такое только в книжках видел и кое где сам реализовывал. Как то так
Re[3]: Жизнь после Dispose (.net)
От: SergeyT. США http://sergeyteplyakov.blogspot.com/
Дата: 08.07.15 18:29
Оценка:
Здравствуйте, 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, которая ставит что-то в очередь. И вот это, ИМХО, очень и очень плохо, поскольку нарушает принцип наименьшего удивления, согласно которому состояние объекта изменяется сразу же после вызова метода, а не когда-то позже.
Re[4]: Жизнь после Dispose (.net)
От: ylem  
Дата: 09.07.15 01:28
Оценка:
Полностью поддерживаю все вышесказанное и не поддерживаю гайдлайны в той строчке.

ST>состояние объекта изменяется сразу же после вызова метода, а не когда-то позже.


Тут можно "выкрутиться" такими способами:

1. состояние объекта меняется сразу после _окончания_ вызова, т.е. при вызове Dispose ставим разрушение в очередь после уже запланированных GetNext().

2. считаем, что время относительно (для разных потоков, пока не сделали какую-нибудь синхронизацию нет понятия "одновременно"), и тогда при (1) не нужно даже блокировать вызов Dispose. Поток, который вызвал Dispose, не сможет различить ситуации изменилось ли состояние объекта "сразу" или "когда-то позже" (заблокируется уже вызов GetNext и вернет false ну или кинет исключение по окончании своей работы).
А по гайдлайнам, кстати, после вызова Dispose вообще не положено узнавать состояние объекта.
Отредактировано 09.07.2015 6:08 ylem . Предыдущая версия .
Re[4]: Жизнь после Dispose (.net)
От: Sinix  
Дата: 09.07.15 06:34
Оценка:
Здравствуйте, SergeyT., Вы писали:

ST>А можно подробнее? А то критиковать что-то, чего автор не имел ввиду — это уже перебор


Там нечего подробнее рассказывать Если в рекомендации есть оговорка — ей не надо пренебрегать, иначе очередной Фаулер получается.


ST>Я подхожу к этому со стороны ООП (сори).

Ну вот в этом и проблема. FDG не про абстрактные идеи в вакууме, и даже не про рекомендации — они играют роль коанов в дзен-буддизме, не больше.
Основная идея книги — "научить разработчиков думать правильно". Т.е. для начала, мыслить не от решения, а от проблемы.

Как только мы принимаем любой довод за "должен соблюдаться всегда", начинаются проблемы. В нашем случае они начинаются, если объект продолжает жить после вызова Dispose(). Например, при асинхронных операциях — как у топикстартера.
Посмотри на observer в Rx, на Task, или на TraceListener. И попробуй применить к ним "должен бросать исключение" с учётом того факта, что любой из них используется из нескольких потоков


ST>Я не говорил об использовании Dispose для асинхронных операций, я писал о вреде "асинхронной" реализации метода Dispose, которая ставит что-то в очередь. И вот это, ИМХО, очень и очень плохо, поскольку нарушает принцип наименьшего удивления, согласно которому состояние объекта изменяется сразу же после вызова метода, а не когда-то позже.


А это вообще абсолютно не важно, потому что у нас и так есть "очередь" потоков и раньше или позже по факту произойдёт Dispose() по факту никакого значения не имеет. Разумеется, при условии что мы подстелили соломки с полем-флагом this.disposed.
Re: Жизнь после Dispose (.net)
От: Коваленко Дмитрий Россия http://www.ibprovider.com
Дата: 13.07.15 09:30
Оценка: 3 (1)
Здравствуйте, 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 можно использовать счетчики. Но уже не ссылок, а внешних вызовов методов объекта.

Работающий пример можно посмотреть здесь.

  Кусок класса OleDbConnection
public sealed class OleDbConnection:DbConnection
                                   ,ICloneable
                                   ,Structure_IComponentLifeTerminator
{
 //...
 public override void Open() //[abstract][MT-SAFE]
 {
  bool lock_flag=false;

  RuntimeHelpers.PrepareConstrainedRegions();       //-------------------- CER

  try
  {
   m_ComponentLifeManager.Lock(this,ref lock_flag,"Open");//throw

   Debug.Assert(lock_flag==true);

   this.Implementation__Open(); //throw
  }
  finally
  {
   if(lock_flag)
    m_ComponentLifeManager.UnLock(this);
  }//finally                                        //-------------------- /CER
 }//Open

 //...
 protected override void Dispose(bool disposing)
 {
  m_ComponentLifeManager.Dispose(this,disposing);
 }//Dispose

 //...
 //Structure_IComponentLifeTerminator interface --------------------------
 void Structure_IComponentLifeTerminator.ComponentLife__ThrowObjectDisposed
       (string ExecMethodName)
 {
  ThrowSysError.object_was_disposed__cant_exec_method
                                           (ErrSourceID.OleDbConnection,
                                            ExecMethodName);
 }//ComponentLife__ThrowObjectDisposed

 //-----------------------------------------------------------------------
 void Structure_IComponentLifeTerminator.ComponentLife__Terminator
       (bool disposing)
 {
  core.Core_Connection CoreConnection=null;

  StateChangeEvent StateChangeHandlers=null;

  //---------------------------------------- Dispose
  if(disposing)
  {
   CoreConnection=Interlocked.Exchange(ref m_CoreConnection,null);

   Debug.Assert(!Object.ReferenceEquals(CoreConnection,null));
   Debug.Assert(Object.ReferenceEquals(m_CoreConnection,null));

   //---------------------------------------
   var InfoMessageHandlers=Interlocked.Exchange(ref m_InfoMessageHandlers,
                                                null);

   StateChangeHandlers=Interlocked.Exchange(ref m_StateChangeHandlers,
                                            null);

   Interlocked.Exchange(ref m_ConnectionOptions,
                        null);

   Interlocked.Exchange(ref m_Guard,
                        null);

   //---------------------------------------
   if(!Object.ReferenceEquals(InfoMessageHandlers,null))
    InfoMessageHandlers.Clear();
  }//if disposing

  base.Dispose(disposing);

  //---------------------------------------- CloseConnection
  ConnectionState prevConnectionState=ConnectionState.Closed;

  if(!Object.ReferenceEquals(CoreConnection,null))
  {
   prevConnectionState=CoreConnection.GetState();

   CoreConnection.CloseConnection(null); //throw?
  }//if

  //---------------------------------------- Fire StateChange Event
  Helper__FireStateChangeEvent(/*disposed!*/this,
                               StateChangeHandlers,
                               prevConnectionState,
                               ConnectionState.Closed);

  if(!Object.ReferenceEquals(StateChangeHandlers,null))
   StateChangeHandlers.Clear();
 }//ComponentLife__Terminator

 //...
 private Structure_ComponentLifeManager
  m_ComponentLifeManager;

 //...
};//class OleDbConnection

  Интерфейс Structure_IComponentLifeTerminator
////////////////////////////////////////////////////////////////////////////////
//LCPI Data OleDb.
//                                               Kovalenko Dmitry. 08.10.2011

namespace lcpi.data.oledb.structure{
////////////////////////////////////////////////////////////////////////////////
//interface Structure_IComponentLifeTerminator

interface Structure_IComponentLifeTerminator
{
 void ComponentLife__ThrowObjectDisposed(string ExecMethodName);

 void ComponentLife__Terminator(bool disposing);
};//interface Structure_IComponentLifeTerminator

////////////////////////////////////////////////////////////////////////////////
}//namespace lcpi.data.oledb.structure

  Исходный код структуры Structure_ComponentLifeManager.
////////////////////////////////////////////////////////////////////////////////
//LCPI Data OleDb.
//                                              Kovalenko Dmitry. 08.10.2011.
using System;
using System.Threading;
using System.Runtime.CompilerServices;
using System.Diagnostics;

namespace lcpi.data.oledb.structure{
////////////////////////////////////////////////////////////////////////////////
//struct Structure_ComponentLifeManager

/// <summary>
///  Object lifetime manager.
/// </summary>
struct Structure_ComponentLifeManager
{
 public void Dispose(Structure_IComponentLifeTerminator pCLT,bool disposing)
 {
  Debug.Assert(!Object.ReferenceEquals(pCLT,null));

  RuntimeHelpers.PrepareConstrainedRegions();  //------------------------- CER

  try
  {
  }
  finally
  {
   if(Interlocked.CompareExchange(ref m_State,
                                  c_ComponentLifeState_BeginDispose,
                                  c_ComponentLifeState_OK)==c_ComponentLifeState_OK)
   {
    Debug.Assert(m_State==c_ComponentLifeState_BeginDispose);

    m_IsDisposing=disposing;

    this.CER__UnLock(pCLT);
   }//if
  }//finally                                   //-------------------------/CER
 }//Dispose
 
 //-----------------------------------------------------------------------
 public void Lock(Structure_IComponentLifeTerminator pCLT,
                  ref bool                           lock_flag,
                  string                             ExecMethodName)
 {
  Debug.Assert(!Object.ReferenceEquals(pCLT,null));

  Debug.Assert(lock_flag==false);

  RuntimeHelpers.PrepareConstrainedRegions();   //------------------------ CER

  try
  {
  }
  finally
  {
   this.CER__Lock(pCLT,ref lock_flag,ExecMethodName);
  }//finally                                    //------------------------/CER
 }//Lock

 //-----------------------------------------------------------------------
 public void TryLock(Structure_IComponentLifeTerminator pCLT,
                     ref bool                           lock_flag)
 {
  Debug.Assert(!Object.ReferenceEquals(pCLT,null));

  Debug.Assert(lock_flag==false);

  RuntimeHelpers.PrepareConstrainedRegions();   //------------------------ CER

  try
  {
  }
  finally
  {
   this.CER__TryLock(pCLT,ref lock_flag);
  }//finally                                    //------------------------/CER
 }//TryLock

 //-----------------------------------------------------------------------
 public void UnLock(Structure_IComponentLifeTerminator pCLT)
 {
  Debug.Assert(!Object.ReferenceEquals(pCLT,null));

  Debug.Assert(m_CntRef>-1);

  RuntimeHelpers.PrepareConstrainedRegions();   //------------------------ CER

  try
  {
  }
  finally
  {
   this.CER__UnLock(pCLT);
  }//finally                                    //------------------------/CER
 }//UnLock

 //-----------------------------------------------------------------------
 /// <summary>
 ///  Testing a state of life
 /// </summary>
 /// <returns>
 ///  <list type="bullet">
 ///   <item>true, if component was not terminated.</item>
 ///   <item>false, if component was terminated.</item>
 ///  </list>
 /// </returns>
 //! \attention
 //!  Use only for debug purpose
 public bool IsAlive()
 {
  return m_State!=c_ComponentLifeState_Terminated;
 }//IsAlive

 //-----------------------------------------------------------------------
 [Conditional("DEBUG")]
 public void Debug__CheckLifeIsLock()
 {
  Debug.Assert((m_State==c_ComponentLifeState_OK && m_CntRef>0) ||
               (m_State==c_ComponentLifeState_BeginDispose));
 }//Debug__CheckLifeIsLock

 //CER methods -----------------------------------------------------------
 private void CER__Lock(Structure_IComponentLifeTerminator pCLT,
                        ref bool                           lock_flag,
                        string                             ExecMethodName)
 {
  Debug.Assert(!Object.ReferenceEquals(pCLT,null));

  Debug.Assert(lock_flag==false);

  if(m_State==c_ComponentLifeState_OK)
  {
   Interlocked.Increment(ref m_CntRef);

   if(m_State==c_ComponentLifeState_OK)
   {
    lock_flag=true;

    return;
   }//if State is OK

   this.CER__UnLock(pCLT);
  }//if State is OK

  Debug.Assert(m_State!=c_ComponentLifeState_OK);

  pCLT.ComponentLife__ThrowObjectDisposed(ExecMethodName);
 }//CER__Lock

 //-----------------------------------------------------------------------
 private void CER__TryLock(Structure_IComponentLifeTerminator pCLT,
                           ref bool                           lock_flag)
 {
  Debug.Assert(!Object.ReferenceEquals(pCLT,null));

  Debug.Assert(lock_flag==false);

  if(m_State==c_ComponentLifeState_OK)
  {
   Interlocked.Increment(ref m_CntRef);

   if(m_State==c_ComponentLifeState_OK)
   {
    lock_flag=true;

    return;
   }//if State is OK

   this.CER__UnLock(pCLT);
  }//if State is OK

  Debug.Assert(m_State!=c_ComponentLifeState_OK);
 }//CER__TryLock

 //-----------------------------------------------------------------------
 private void CER__UnLock(Structure_IComponentLifeTerminator pCLT)
 {
  Debug.Assert(!Object.ReferenceEquals(pCLT,null));

  Debug.Assert(m_CntRef>-1);

  if(Interlocked.Decrement(ref m_CntRef)==-1)
   this.CER__TryTerminate(pCLT);
 }//CER__UnLock

 //-----------------------------------------------------------------------
 private void CER__TryTerminate(Structure_IComponentLifeTerminator pCLT)
 {
  Debug.Assert(!Object.ReferenceEquals(pCLT,null));

  Debug.Assert(m_State!=c_ComponentLifeState_OK);

  if(Interlocked.CompareExchange
      (ref m_State,
       c_ComponentLifeState_Terminated,
       c_ComponentLifeState_BeginDispose)!=c_ComponentLifeState_BeginDispose)
  {
   Debug.Assert(m_State==c_ComponentLifeState_Terminated);

   return;
  }//if prevState!=c_ComponentLifeState_BeginDispose

  pCLT.ComponentLife__Terminator(m_IsDisposing);
 }//CER__TryTerminate

 //----------------------------------------- Component Life State
 private const int c_ComponentLifeState_OK             =0;
 private const int c_ComponentLifeState_BeginDispose   =1;
 private const int c_ComponentLifeState_Terminated     =2;

 //-----------------------------------------
 private int m_CntRef;//default: 0

 private int m_State;//default: 0

 private bool m_IsDisposing;//default: false
};//Structure_ComponentLifeManager

////////////////////////////////////////////////////////////////////////////////
}//namespace lcpi.data.oledb.structure

Думаю, для осознания вышеобозначенного подхода потребуется не более двух стаканов.
-- Пользователи не приняли программу. Всех пришлось уничтожить. --
Отредактировано 02.05.2017 7:42 DDDX . Предыдущая версия .
Re[5]: Жизнь после Dispose (.net)
От: Venom  
Дата: 06.08.15 11:15
Оценка: +1
Здравствуйте, Sinix, Вы писали:

S>[/cs] Но лучший сборник примеров — это безусловно Rx. Там есть всё.


Что-то у них на сайте контент поломался. Гугл и сам сайт ссылаются именно туда, значит дело в самом сайте.
Вот pdf-версия книги introToRX на гугл-драйве, там эта глава есть (последние две страницы книги):
Re: Жизнь после Dispose (.net)
От: TK Лес кывт.рф
Дата: 13.09.15 11:40
Оценка:
Здравствуйте, ylem, Вы писали:

Y>2. вызовы к GetNext() и Dispose() асинхронные, но внутри встают в очередь. Сделал так, что GetNext(), начавшиеся до Dispose, результат вернут, а начавшиеся после -- нет.


Y>На сколько 1 и 2 "правильно" и соответствует ожиданиям от IDisposable?


Так надо использовать не классы, а интерфейсы. Один IGetNext с методом GetNext, другой IDisposable с методом Dispose
Потребители IGetNext ничего про IDisposable не знают и за что им кидать ObjectDisposedException — непонятно.
А тем потребителям что знают про IDisposable ничего про IGetNext говорить не надо — так и вопросов "где мой ObjectDisposedException" никаких не возникнет.
Если у Вас нет паранойи, то это еще не значит, что они за Вами не следят.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.