2009-06-23 18 views
17

Recientemente, estaba investigando algunos errores complicados sobre objetos no eliminados.¿Cómo manejar la excepción lanzada desde Eliminar?

Encontré algunos patrones en el código. Se informa que algunos m_foo no están eliminados, mientras que parece que todas las instancias de SomeClass han sido eliminadas.

public class SomeClass: IDisposable 
{ 
    void Dispose() 
    { 
     if (m_foo != null) 
     { 
      m_foo.Dispose(); 
     } 
     if (m_bar != null) 
     { 
      m_bar.Dispose(); 
     } 
    } 

    private Foo m_foo; 

    private Bar m_bar; 

} 

que sospecha que Foo.Dispose podría lanzar una excepción, por lo que después de código no se ejecuta de manera m_bar no está dispuesto.

Dado que Foo/Bar puede ser de un tercero, por lo tanto no se garantiza que no arroje una excepción.

Si solo envuelve todo Eliminar invocación con try-catch, el código se volverá torpe.

¿Cuál es la mejor práctica para manejar esto?

Respuesta

3

Si se llama a Dispose() dentro de un contexto de finalización y arroja una excepción, su proceso finalizará.

Si sospecha que Foo.Dispose() arroja una excepción, me gustaría deshacerme de ella la última vez que sea posible, y envolverla en try/catch. Haga todo lo posible para deshacerse de él en la captura: establezca la referencia a nulo. Es muy malo lanzar excepciones de Dispose() y debe evitarse.

Desafortunadamente, si se trata de un código de error de un tercero, la mejor opción sería conseguir que lo arreglen. No debería tener que limpiar manualmente después de esto.

Espero que ayude.

+2

@womp, su perder perder, si oculta/controlar una excepción arbitraria que se produce a partir de disponer que puede terminar perdiendo memoria o asas o bloqueos del sistema operativo. Todo lo cual puede causar estragos en su proceso. –

+0

Muy cierto. Asumía que quería mantener el proceso a toda costa, pero probablemente debería haber dicho eso.Su mejor opción sería tirar el control o conseguir que el tercero lo arregle. Sus comentarios sobre "difícil de depurar" es un excelente punto. – womp

25

Es cierto que puede ser bastante malo filtrar una excepción de su método de eliminación, especialmente porque las cosas que implementa IDisposable generalmente especifican un finalizador que llamará a Dispose.

El problema es que solucionar el problema debajo de la alfombra al manejar una excepción puede dejarle algunas situaciones muy difíciles de depurar. ¿Qué pasa si su IDisposable asignó una sección crítica de tipo que solo se libera después de deshacerse de ella? Si ignora el hecho de que ocurrió la excepción, puede terminar en un punto muerto central. Creo que las fallas en Dispose deberían ser uno de esos casos en los que quieres fallar temprano, para que puedas corregir el error tan pronto como se descubra.

Por supuesto, todo depende del objeto que se va a eliminar, para algunos objetos que puede recuperar, otros no. Como regla general, Dispose no debería lanzar excepciones cuando se usa correctamente y no debería tener que codificar de forma defensiva las excepciones en los métodos de Disposición anidada que está llamando.

¿De verdad no quiere barrer una OutOfMemoryException debajo de la alfombra?

Si tuviera un componente dudoso de un tercero que arbitrariamente arrojara excepciones en Dispose lo arreglaría Y lo alojaría en un proceso separado que podría demoler cuando comenzara a reproducirse.

+2

+1 por difícil de depurar. – womp

1

Dado que no tiene que asignar las variables en una instrucción using(), ¿por qué no utilizar las instrucciones de uso 'apiladas' para esto?

void Dispose() 
{ 
    // the example in the question didn't use the full protected Dispose(bool) pattern 
    // but most code should have if (!disposed) { if (disposing) { ... 

    using (m_foo) 
    using (m_bar) 
    { 
     // no work, using statements will check null 
     // and call Dispose() on each object 
    } 

    m_bar = null; 
    m_foo = null; 
} 

las instrucciones using 'apilados' se expanden como esto:

using (m_foo) 
{ 
    using (m_bar) { /* do nothing but call Dispose */ } 
} 

Así las() llama Desechar se ponen en separadas por último bloques:

try { 
    try { // do nothing but call Dispose 
    } 
    finally { 
     if (m_bar != null) 
      m_bar.Dispose(); 
    } 
finally { 
    if (m_foo != null) 
     m_foo.Dispose(); 
} 

que tenía un tiempo difícil encontrar una referencia para esto en un solo lugar. Las instrucciones de uso 'apiladas' se encuentran en un old Joe Duffy blog post (consulte la sección 'C# y VB Using Statement, C++ Stack Semantics'). La publicación de Joe Duffy es referenciada por muchas respuestas de StackOverflow en IDisposable. También encontré un recent question donde las declaraciones apiladas utilizando para las variables locales parecen ser comunes. No pude encontrar el encadenamiento de los bloques finally en cualquier lugar excepto el C# language spec (sección 8.13 en la especificación C# 3.0), y solo para múltiples variables dentro de un solo bloque 'using', que no es exactamente lo que propongo, pero si Desenmascarar la IL encontrará que los bloques try/finally están anidados. En la comprobación nula, también desde la especificación C#: 'Si se adquiere un recurso nulo, no se realiza ninguna llamada a Dispose y no se lanza ninguna excepción'.

0

Para evitar la repetición de código para eliminar objetos, escribí el siguiente método estático.

public static void DisposeObject<T>(ref T objectToDispose) where T : class 
    { 
     IDisposable disposable = objectToDispose as IDisposable; 
     if (disposable == null) return; 

     disposable.Dispose(); 
     objectToDispose = null; 
    } 

El punto principal es que se puede hacer una función, por lo que sólo el tipo una línea por objeto quiere desechar, manteniendo los métodos Desechar agradable y limpio. En nuestro caso, era una convención anular los punteros dispuestos, de ahí los parámetros de ref.

En su caso, es posible que desee agregar el manejo de excepciones o crear un sabor diferente con manejo de excepciones. Me aseguraré de que inicie sesión/punto de interrupción siempre que un Dispose() arroje excepciones, pero si no puede evitarlo, la siguiente mejor opción es asegurarse de que el problema no se propague.

1

Dispose no debe arrojar ninguna excepción. Si lo hace, no está bien escrito, entonces ...

try { some.Dispose(); } catch {} 

debería ser suficiente.

+1

Es cierto que, cuando sea posible, se debe diseñar la semántica de Dispose para que no falle, pero el mundo real no siempre es tan agradable. La eliminación de un archivo, por ejemplo, se supone que garantiza que se escriba realmente cualquier dato pendiente. Si los datos no se escriben, es un problema y debe comunicarse de alguna manera. Sugeriría que las excepciones no deberían arrojarse a menos que algo realmente esté realmente mal, pero si algo realmente está realmente mal, no debería ser ignorado. – supercat

+0

Veo tu punto. Sin embargo, Dispose no debe arrojar ninguna excepción. Los datos pendientes se pueden forzar a escribir en un método Flush o similar. Lo que quiero decir es que siempre tienes la posibilidad de escribir código de forma que garantice que Dispose no arroje nada. Pero si tiene que tratar con código externo que arroja una excepción en Dispose, lo envolvería en otro método, como TryDispose, y reescribiré Dispose de manera adecuada. – LukeSw

+0

@LukeSw: si el código hace suposiciones sobre el estado del sistema después de una Disposición, y esas suposiciones no se cumplen, Dispose debe emitir una excepción. Desafortunadamente, no hay una buena forma de saber qué supuestos va a hacer el código externo, ni hay una buena forma de saber qué excepción (si hay alguna) está pendiente cuando se produce una falla en la eliminación. Si algún código espera que una transacción de base de datos tenga éxito o deje la base de datos en un estado conocido sin alterar, y por alguna razón se produce una excepción, pero la base de datos no puede hacer una reversión y confirmar que funcionó ... – supercat

1

Según Design Rules:

"Un método IDisposable.Dispose no debe lanzar una excepción."

Así que si su programa se bloquea debido a excepción no controlada de Dispose() - se refiere Official Solution

0

En mi caso fue a causa de un hilo acceso a elemento de la interfaz al cerrar el formulario. Lo resolví abortando el hilo en la forma cercana. (Evento "FormClosing")

FormClosing += (o, e) => worker.Abort(); 
Cuestiones relacionadas