Cita de: Devilkeeper en 8 Enero 2018, 21:35 PMNo sé si es una manera correcta de trabajar, o es mejor cargar los Puestos y según necesite ir cargando y descargando las listas. Si quisiera cargar todos los puestos, tendría en memoría muchísimos datos, y no sé si eso está bien.
Es precisamente por esa necesidad que existe la interfáz IEnumerable<T> (o IEnumerable(Of T) en VB.NET) de evaluación vaga y el tipo Lazy<T> (o Lazy(Of T) en VB.NET) para la inicialización/instanciación vaga de objetos.
La interfáz IEnumerable, o colección enumerable, provee lo que se conoce como un enumerador, que es un mecanismo para obtener el elemento actual en la colección enumerable, mover al siguiente elemento, y reiniciar. El enumerador solamente debe preocuparse por saber como obtener el siguiente elemento en la colección enumerable, por lo que no es necesario que la colección entera esté alojada en memoria ni saber cuantos elementos hay en total; este comportamiento o implementación se traduce en una optimización de varios aspectos, empezando por el más obvio: el consumo de memoria.
Practicamente todas las colecciones disponibles en los espacios de nombre de la librería de .NET Framework implementan la la interfáz IEnumerable, como los tipos List, Dictionary, Array, Stack, Collection y etcétera, pero ojo, eso no quiere decir que todos los tipos de colecciones sean de evaluación vaga, cada tipo tiene una implementación distinta para servir a un propósito específico, así que si quieres asegurarte de beneficiarte de la evaluación vaga entonces usa directamente la interfáz IEnumerable.
El tipo Lazy<T>, por lo general lo usarías en escenarios de programación asincrónica y para prevenir la inicialización de una instancia que sea bastante pesada... hasta que realmente necesites acceder/leer ese objeto.
Te recomiendo unas lecturas importantes para comprender los conceptos básicos:
- Lazy Evaluation | Wikipedia
- Eager evaluation | Wikipedia
- Lazy Initialization | docs.microsoft.com
- How to: Perform Lazy Initialization of Objects | docs.microsoft.com[/url
CitarLazy initialization of an object means that its creation is deferred until it is first used. (For this topic, the terms lazy initialization and lazy instantiation are synonymous.) Lazy initialization is primarily used to improve performance, avoid wasteful computation, and reduce program memory requirements.
CitarBy initializing objects in a lazy manner, you can avoid having to create them at all if they are never needed, or you can postpone their initialization until they are first accessed
Bien, ahora que ya he explicado que es cada cosa, te mostraré como puedes reproducir las diferencias a través de varios ejemplos (básicos) basados en el escenario o problema que propones...
Primero de todo, para estos ejemplos crearemos un tipo como éste para representar la información básica de un cliente:
Código (vbnet) [Seleccionar]
Public NotInheritable Class Customer
Public Property Name As String
Public Property Surname As String
Public Property Buffer As Byte()
Public ReadOnly Property InstanceId As Guid
Get
If (Me.instanceIdB = Guid.Empty) Then
Me.instanceIdB = Guid.NewGuid()
End If
Return Me.instanceIdB
End Get
End Property
Private instanceIdB As Guid
Private Sub New()
End Sub
Public Sub New(name As String, surname As String)
Me.Name = name
Me.Surname = surname
Me.Buffer = New Byte(4096) {}
End Sub
Public Overrides Function ToString() As String
Return Me.InstanceId.ToString()
End Function
End Class
...es solo un ejemplo cualquiera, mi intención al escribir este ejemplo ha sido que una instancia del tipo Customer ocupe bastantes bytes.
Bien, por lo general, podriamos decidir crear una colección genérica de tipo List<T> (o List(Of T) en VB.NET) para almacenar varias instancias de la clase Customer, e iterar la colección para mostrar cualquier tipo de información de cada Customer, quedando algo parecido a esto:
Código (vbnet) [Seleccionar]
Imports System
Imports System.Collections.Generic
Imports System.Threading
Public Module Module1
Private customers As List(Of Customer)
Private customerCount As Integer
Private Sub BuildCustomers(ByRef collection As List(Of Customer), ByVal amount As Integer)
If (collection Is Nothing) Then
collection = New List(Of Customer)
ElseIf (collection.Any) Then
collection.Clear()
End If
For i As Integer = 0 To (amount - 1)
collection.Add(New Customer(String.Empty, String.Empty))
Next i
End Sub
Public Sub Main()
' Construimos la lista de clientes.
Module1.BuildCustomers(Module1.customers, 100000)
' En este punto, la instanciación de todos los objetos en la colección
' habrá alcanzado un consumo de memoria sobre los 500 megabytes aprox.
' Mostramos la cantidad de elementos en la lista
' (la propiedad Count debe realizar una iteración completa, así que puede tardar unos segundos).
Console.WriteLine(String.Format("Customers Count: {0}", Module1.customers.Count))
' Iteramos cada elemento en la lista.
For Each c As Customer In Module1.customers
Console.WriteLine(String.Format("Customer {0}; Unique Id: {1}",
Interlocked.Increment(Module1.customerCount), c.ToString()))
Next c
' Ya no necesitamos la lista, así que liberarariamos recursos administrados innecesarios,
' y forzamos una recolección del GarbageCollector para un efecto inmediato.
Module1.customers.Clear()
Module1.customers = Nothing
GC.Collect()
GC.WaitForPendingFinalizers()
GC.WaitForFullGCApproach()
GC.WaitForFullGCComplete()
' En este punto, la carga de objetos en memoria se reduce al máximo posible,
' en mi caso el consumo total es de 80 mb aprox.
Console.WriteLine("Press any key to exit...")
Console.ReadKey(intercept:=True)
Environment.Exit(0)
End Sub
End Module
¿Qué ocurre con esto?, pues que todos los elementos de la colección se inicializan, se asigna un espacio en el bloque de memoria para cada elemento de la colección, y evidentemente si tenemos en cuenta el tamaño en bytes de una única instancia de la classe Customer pues... 100.000 instancias simultaneas resultan en un gran consumo de memoria...
Cuando un elemento Customer ha sido inicializado y ya hemos trabajado con él, no necesitamos que siga albergando espacio en memoria, queremos desechar ese consumo adicional que ya no necesitamos para nada, y aquí es donde .NET Framework nos ofrece la solución al problema: IEnumerable<T>.
Este código de aquí abajo es practicamente lo mismo que el anterior con el uso de List<T>, solo que ha sido adaptado para el uso de IEnumerable<T> mediante una función iteradora y así demostrar el beneficio que nos interesa obtener...
Código (vbnet) [Seleccionar]
Imports System
Imports System.Collections.Generic
Imports System.Threading
Public Module Module1
Private customers As IEnumerable(Of Customer)
Private customerCount As Integer
Private Iterator Function BuildCustomers(ByVal amount As Integer) As IEnumerable(Of Customer)
For i As Integer = 0 To amount
Yield New Customer(String.Empty, String.Empty)
Next i
End Function
Public Sub Main()
' Construimos la colección de clientes.
Module1.customers = Module1.BuildCustomers(100000)
' En este punto, el consumo de memoria es mínimo, alcanzando los 12 mb aprox.
' Mostramos la cantidad de elementos en la colección
Console.WriteLine(String.Format("Customers Count: {0}", Module1.customers.Count))
' Iteramos cada elemento en la colección.
For Each c As Customer In Module1.customers
Console.WriteLine(String.Format("Customer {0}; Unique Id: {1}",
Interlocked.Increment(Module1.customerCount), c.ToString()))
Next c
' En este punto, el consumo de memoria es mínimo, sin cambios, puesto que al iterar, los elementos se han evaluado de forma vaga.
Module1.customers = Nothing
Console.WriteLine("Press any key to exit...")
Console.ReadKey(intercept:=True)
Environment.Exit(0)
End Sub
End Module
El beneficio en la optimización del consumo de memoria queda bastante claro:
Te muestro un ejemplo para el uso del tipo Lazy:
Código (vbnet) [Seleccionar]
Imports System
Imports System.Collections.Generic
Imports System.Threading
Public Module Module1
Private lazyCustomers As List(Of Lazy(Of Customer))
Private customerCount As Integer
Private Function BuildCustomers(ByVal amount As Integer) As List(Of Lazy(Of Customer))
Dim collection As New List(Of Lazy(Of Customer))
For i As Integer = 0 To amount
collection.Add(New Lazy(Of Customer)(
Function() As Customer
Return New Customer(String.Empty, String.Empty)
End Function))
Next i
Return collection
End Function
Public Sub Main()
' Construimos la instancia de inicialización vaga con la colección de clientes.
Module1.lazyCustomers = Module1.BuildCustomers(100000)
' En este punto, el consumo de memoria es mínimo, alcanzando los 12 mb aprox,
' puesto que todavía NO hemos inicializado ningún elemento de la colección.
Console.WriteLine(String.Format("Lazy Customers Count: {0}", Module1.lazyCustomers.Count))
For Each lz As Lazy(Of Customer) In Module1.lazyCustomers
Console.WriteLine(String.Format("Customer {0}; Unique Id: {1}",
Interlocked.Increment(Module1.customerCount), lz.Value.ToString()))
Next lz
' Cuando accedemos a la propiedad 'Lazy(Of T).Value' es cuando inicializamos el objecto de inicialización vaga,
' así que al terminar la iteración hemos inicializaco todos los elementos, por lo que en este punto el consumo de memoria rondará los 500 megabytes aprox, como en el primer ejemplo.
' Ya no necesitamos la colección, así que liberarariamos recursos administrados innecesarios,
' y forzamos una recolección del GarbageCollector para un efecto inmediato.
Module1.lazyCustomers.Clear()
Module1.lazyCustomers = Nothing
GC.Collect()
GC.WaitForPendingFinalizers()
GC.WaitForFullGCApproach()
GC.WaitForFullGCComplete()
' En este punto, la carga de objetos en memoria se reduce al máximo posible,
' en mi caso el consumo total es de 20 mb aprox.
Console.WriteLine("Press any key to exit...")
Console.ReadKey(intercept:=True)
Environment.Exit(0)
End Sub
End Module
Por supuesto podemos combinar el tipo Lazy<T> e IEnumerable<T>:
Código (vbnet) [Seleccionar]
Imports System
Imports System.Collections.Generic
Imports System.Threading
Public Module Module1
Private lazyCustomers As IEnumerable(Of Lazy(Of Customer))
Private customerCount As Integer
Private Iterator Function BuildCustomers(ByVal amount As Integer) As IEnumerable(Of Lazy(Of Customer))
For i As Integer = 0 To amount
Yield New Lazy(Of Customer)(
Function() As Customer
Return New Customer(String.Empty, String.Empty)
End Function)
Next i
End Function
Public Sub Main()
' Construimos la instancia de inicialización vaga con la colección de clientes.
Module1.lazyCustomers = Module1.BuildCustomers(100000)
' En este punto, el consumo de memoria es mínimo, alcanzando los 12 mb aprox,
' puesto que todavía NO hemos inicializado ningún elemento de la colección.
Console.WriteLine(String.Format("Lazy Customers Count: {0}", Module1.lazyCustomers.Count))
For Each lz As Lazy(Of Customer) In Module1.lazyCustomers
Console.WriteLine(String.Format("Customer {0}; Unique Id: {1}",
Interlocked.Increment(Module1.customerCount), lz.Value.ToString()))
' Cuando accedemos a la propiedad 'Lazy(Of T).Value' es cuando inicializamos el objecto de inicialización vaga.
Next lz
' Al terminar la iteración, en este punto no hay cambios en el consumo de memoria,
' puesto que hemos utilizado una colección enumerable.
Module1.lazyCustomers = Nothing
Console.WriteLine("Press any key to exit...")
Console.ReadKey(intercept:=True)
Environment.Exit(0)
End Sub
End Module
IEnumerable y Lazy son dos propuestas para fines distintos, aunque combinables para un mismo fin, y yo solo te he mostado ejemplos básicos de su utilización en escenarios sincrónicos; al final la solución más óptima y adecuada a tu problema siempre dependerá de lo que realmente quieras hacer y como sea realmente necesario hacerlo...
Espero que esto haya servido de ayuda, al menos.
Saludos.