2011-04-19 25 views
17

Si recuerdo correctamente que cuando usé yield dentro de los bloques using SqlConnection obtuve excepciones de tiempo de ejecución.Rendimiento de rendimiento dentro de los usos

using (var connection = new SqlConnection(connectionString)) 
{ 
    var command = new SqlCommand(queryString, connection); 
    connection.Open(); 

    SqlDataReader reader = command.ExecuteReader(); 

    // Call Read before accessing data. 
    while (reader.Read()) 
    { 
     yield reader[0]; 
    } 

    // Call Close when done reading. 
    reader.Close(); 
} 

Esos problemas se resolvieron cuando reemplacé yield por una lista en la que los elementos añadidos cada iteración.

El mismo problema no ocurrió sin embargo a mí cuando dentro using StreamReader bloques

using (var streamReader = new StreamReader(fileName)) 
{ 
    string line; 
    while ((line = streamReader.ReadLine()) != null) 
    { 
     yield return line; 
    } 
} 

¿Hay alguna explicación de por qué excepciones ocurrieron en el primer caso y no en el segundo? ¿Es aconsejable esta construcción?

EDITAR para obtener el error (la eliminación temprana) que hice en el pasado, usted debe llamar el primer método a continuación:

IEnumerable<string> Read(string fileName) 
{ 
    using (var streamReader = new StreamReader(fileName)) 
    { 
     return Read(streamReader); 
    } // Dispose will be executed before ReadLine() because of deffered execution 
} 

IEnumerable<string> Read(StreamReader streamReader) 
{ 
    string line; 
    while ((line = streamReader.ReadLine()) != null) 
    { 
     yield return line; 
    } 
} 

El mismo error se puede lograr con otras formas de aplazamiento de la ejecución, tales como System.Linq.Enumerable.Select()

+3

Lo excepción Qué se obtiene a partir del rendimiento de SQL? – rossisdead

+0

Si tiene reflector, eche un vistazo al código producido por ambos ejemplos de código. Debería arrojar luz sobre por qué uno arroja y uno no. –

+0

@rossisdead Como señaló LukeH, una posible excepción es TimeoutException –

Respuesta

3

Ver this post para una buena explicación de los problemas con using y yield. Como regresas en el enumerador, el bloque de uso ya habrá destruido el contexto antes de acceder a cualquier cosa. Las respuestas tienen buenas soluciones, básicamente, o bien hacen que el método wrapper sea un enumerador, o crean una lista en su lugar.

También es generalmente más práctico tener using alrededor del lector, no la conexión, y usar CommandBehavior.CloseConnection para asegurar que los recursos se liberen cuando el lector termine. Aunque realmente no importa en su situación, si alguna vez devuelve un lector de datos de un método, esto asegurará que la conexión se cierre correctamente cuando se deseche el lector.

using(SqlDataReader reader = 
      command.ExecuteReader(CommandBehavior.CloseConnection)) { 
     while (reader.Read()) 
     { 
      yield reader[0]; 
     } 
    } 
+0

Tienes razón. 'yield return's se puede ejecutar en disposiciones anticipadas si la directiva' using' no está en el mismo método. Esta es probablemente la excepción que recuerdo vagamente cuando publiqué esta pregunta. –

+3

En esta situación, aplicaría 'using' tanto al Reader _ como a la Connection. –

+0

Acepto que es una buena práctica, pero las conexiones SQL son un caso especial porque es una práctica normal (y es compatible con 'CommandBehavior.CloseConnection') crear un lector de datos y pasarlo fuera del contexto en el que se creó.'Dispose' en una conexión simplemente llama a' Close' (y borra la cadena de conexión). Entonces, en ese sentido, no es realmente necesario usar '' usar '' alrededor de las conexiones, y la forma en que se crean muchos subsistemas de administración de bases de datos no sería práctico. –

2

El compilador debe manejar el yield dentro del bloque using correctamente en ambos casos. No hay una razón obvia por la que debería lanzar una excepción.

Una cosa a tener en cuenta es que la conexión solo se eliminará una vez que complete la iteración y/o elimine manualmente el objeto del enumerador. Si expone este código en un método público, entonces es posible que el código malicioso podría estúpidos o mantener la conexión abierta durante mucho tiempo:

var enumerable = YourMethodThatYieldsFromTheDataReader(); 
var enumerator = enumerable.GetEnumerator(); 
enumerator.MoveNext(); 
Thread.Sleep(forever); // your connection will never be disposed 
+0

Eso tiene sentido. Una posible excepción es una TimeoutException. –

Cuestiones relacionadas