Информация об изменениях

Сообщение Re: Жизнь после Dispose (.net) от 13.07.2015 9:30

Изменено 02.05.2017 7:42 DDDX

Re: Жизнь после Dispose (.net)
Здравствуйте, 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

Думаю, для осознания вышеобозначенного подхода потребуется не более двух стаканов.
Re: Жизнь после Dispose (.net)
Здравствуйте, 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

Думаю, для осознания вышеобозначенного подхода потребуется не более двух стаканов.