c# - lazy net
Desventajas de Lazy<T>? (7)
Recientemente comencé a usar Lazy en mi aplicación, y me preguntaba si hay algún aspecto negativo obvio que deba tener en cuenta al usar Lazy<T>
?
Estoy tratando de utilizar Lazy<T>
tantas veces como lo considere apropiado, principalmente para ayudar a reducir la huella de memoria de nuestros complementos cargados, pero inactivos.
¿Qué quiere decir exactamente con "a lo largo de mi solicitud" ?
Creo que solo debería usarse cuando no esté seguro de si el valor se usará o no, lo que puede ser el caso únicamente con parámetros opcionales que tardan mucho en computarse. Esto podría incluir cálculos complejos, manejo de archivos, servicios web, acceso a bases de datos, etc.
Por otro lado, ¿por qué usar Lazy
aquí? En la mayoría de los casos, simplemente puede llamar a un método en lugar de lazy.Value
y de todos modos no hace ninguna diferencia. PERO es más simple y obvio para el programador lo que está sucediendo en esta situación sin Lazy
.
Una ventaja obvia es que ya se ha implementado el almacenamiento en caché del valor, pero no creo que sea una gran ventaja.
Aquí no es un aspecto bastante negativo, sino un problema para la gente perezosa :).
Los inicializadores diferidos son como los inicializadores estáticos. Ellos corren una vez . Si se lanza una excepción, la excepción se almacena en caché y las llamadas subsiguientes a .Value lanzarían la misma excepción. Esto es por diseño y se menciona en los documentos ... http://msdn.microsoft.com/en-us/library/dd642329.aspx :
Las excepciones lanzadas por valueFactory se almacenan en caché.
Por lo tanto, el código como a continuación nunca devolverá un valor:
bool firstTime = true;
Lazy<int> lazyInt = new Lazy<int>(() =>
{
if (firstTime)
{
firstTime = false;
throw new Exception("Always throws exception the very first time.");
}
return 21;
});
int? val = null;
while (val == null)
{
try
{
val = lazyInt.Value;
}
catch
{
}
}
Como con cualquier cosa, Lazy<T>
se puede usar para bien o para mal, por lo tanto, una desventaja: cuando se usa de manera inapropiada, puede causar confusión y frustración. Sin embargo, el patrón de inicialización lento ha existido durante años, y ahora que .NET BCL tiene una implementación, los desarrolladores no necesitan reinventar la rueda una vez más. Además, MEF ama a Lazy .
En mi opinión, siempre debes tener una razón para elegir a Lazy. Existen varias alternativas según el caso de uso y definitivamente hay casos en los que esta estructura es apropiada. Pero no lo use solo porque es genial.
Por ejemplo, no entiendo el punto en el ejemplo de selección de página en una de las otras respuestas. El uso de una lista de Lazy para seleccionar un solo elemento se puede hacer bien con una lista o diccionario de delegados directamente sin usar Lazy o con una simple declaración de cambio.
Entonces, las alternativas más obvias son
- instanciación directa para estructuras de datos o estructuras baratas que de todos modos son necesarias
- delega para cosas que son necesarias cero o pocas veces en algún algoritmo
- alguna estructura de almacenamiento en caché para elementos que deberían liberar la memoria cuando no se usa durante un tiempo
- algún tipo de estructura "futura" como Tarea que ya puede comenzar a inicializarse de forma asíncrona antes de que el uso real consuma tiempo de CPU inactivo en casos donde la probabilidad es bastante alta de que la estructura se requiera más adelante
En contraste con eso, Lazy suele ser adecuado cuando
- estructuras de datos computacionalmente intensas
- son necesarios cero o muchas veces en algún algoritmo donde el caso cero tiene una probabilidad significativa
- y los datos son locales para algún método o clase y pueden ser recogidos cuando ya no están en uso o los datos deben mantenerse en la memoria durante todo el tiempo de ejecución del programa
Expandiré un poco mi comentario, que dice:
Acabo de empezar a utilizar Lazy, y descubro que a menudo es indicativo de un mal diseño; o pereza por parte del programador. Además, una desventaja es que tienes que estar más atento con las variables delimitadas y crear cierres adecuados.
Por ejemplo, he usado Lazy<T>
para crear las páginas que el usuario puede ver en mi aplicación MVC (sin sesión ). Es un asistente de guía, por lo que el usuario podría querer ir a un paso anterior al azar. Cuando se realiza el protocolo de enlace, se organiza una matriz de objetos Lazy<Page>
, y si el usuario especifica como paso, se evalúa esa página exacta. Creo que ofrece un buen rendimiento, pero hay algunos aspectos que no me gustan, por ejemplo, muchos de mis constructos foreach
ahora se ven así:
foreach(var something in somethings){
var somethingClosure = something;
list.Add(new Lazy<Page>(() => new Page(somethingClosure));
}
Es decir, debe enfrentar el problema de los cierres de manera muy proactiva. De lo contrario, no creo que sea un golpe de rendimiento tan malo almacenar una lambda y evaluarla cuando sea necesario.
Por otro lado, esto podría ser indicativo de que el programador es un Lazy<Programmer>
, en el sentido de que preferiría no pensar en su programa ahora, y dejar que la lógica apropiada lo evalúe cuando sea necesario, como en mi caso, por ejemplo. - en lugar de construir esa matriz, podría averiguar exactamente cuál sería esa página específica solicitada; pero elegí ser flojo y hacer un acercamiento total.
EDITAR
Se me ocurre que Lazy<T>
también tiene algunos peculiares cuando trabaja con concurrencia. Por ejemplo, hay un ThreadLocal<T>
para algunos escenarios y varias configuraciones de indicadores para su escenario concreto de múltiples subprocesos. Puedes leer más en msdn .
He venido a usar Lazy<T>
principalmente debido a sus capacidades de concurrencia al cargar recursos desde la base de datos. Así me deshice de los objetos de bloqueo y los patrones de bloqueo discutibles. En mi caso, ConcurrentDictionary
+ Lazy
como un valor hizo mi día, gracias a @Reed Copsey y su publicación en el blog
Esto se parece a lo siguiente. En lugar de llamar:
MyValue value = dictionary.GetOrAdd( key, () => new MyValue(key));
En su lugar, utilizaríamos un ConcurrentDictionary> y escribiríamos:
MyValue value = dictionary.GetOrAdd( key, () => new Lazy<MyValue>( () => new MyValue(key))) .Value;
No hay inconvenientes de Lazy<T>
notado hasta ahora.
Lazy se usa para preservar recursos cuando realmente no se necesita. Este patrón es bastante bueno, pero la implementación puede ser inútil.
Cuanto más grande es el recurso, más útil es este patrón.
Una desventaja de usar la clase Lazy es la falta de transparencia de uso. De hecho, debe mantener en todas partes un indirecto adicional (.Value). Cuando solo necesita una instancia de tipo real, se fuerza a cargar incluso si no necesita usarla directamente.
Lazy es para el desarrollo perezoso, ganando productividad, pero esta ganancia puede perderse por el alto uso.
Si tiene una implementación realmente transparente (usando un patrón proxy por ejemplo), se deshace de la desventaja y puede ser muy útil en muchos casos.
La concurrencia se debe considerar en otro aspecto y no se implementa por defecto en su tipo. Se debe incluir solo en el código del cliente o escriba helpers para este concepto.