This article will present two alternatives to
Lazy. One of them is focused on low memory use and the other focused on responsiveness.
.NET 4 added the
Lazy class. Its purpose is to allow fast load times and reduce memory consumption.
But, should I say that I consider
Lazy class... too lazy? There are two problems that I see when using the
- The application loads fast, but then you must wait for each item that you access for the first time.
- If you load an item to use it only once, it will be kept in memory forever (ok... until the
lazy object itself is collected).
So, to solve those problems, I decided to create my own solution, which I will present here.
I really wanted to create a solution that is both responsive and that avoids using memory when it is not needed. But unfortunately that's impossible... at least for me and as a generic purpose solution. But I can solve one of those problems at a time.
So I did it, as two different classes:
LazyAndWeak - Better memory management
BackgroundLoader - Better responsiveness
For human beings, being lazy is usually bad. Being weak too is even worse. But for programming languages, being lazy and weak is good, especially when we want to save memory.
LazyAndWeak only creates its
Value when it is first requested, as happens with
Lazy class. But it also stores the value as a weak-reference, so it can be collected if memory is needed. Ok, it is a bit more than that. To avoid values to be collected at each garbage collection, I use my
GCUtils.KeepAlive method so recently used items are not collected in the next full collection, so constantly used items will always be in memory, even after a full collection.
The big downside here is that after the item is unloaded from memory, if it is needed again, the user will need to wait again and any value that was stored in such item will be lost. So, it is very important that the constructor/creator knows how to get the most up-to-date information.
BackgroundLoader class goes in the opposite direction of
LazyAndWeak. Instead of creating items only when requested and then allowing them to die, it tries to load all items in the background, trying not to affect performance of the application and then keeps them there. So, you load the application in a very fast manner, similar to what happens to any
Lazy alternative but then when a CPU is idle, it starts loading the items that you didn't use yet.
I say that it goes in the opposite direction because it will not save any memory, as sooner or later all the items will be loaded and kept in memory. But except from the very first item (in case the user requests it as soon the application is loaded), the user will not need to wait for items to load. It opens the application (in a very fast manner), maybe wait for the very first operation to load its items but, later, any other operation will be done instantly as the items will be already loaded.
When To Use Each One?
In my opinion, never use
Lazy. Maybe I am too drastic, but I don't see a real situation were
Lazy only will be better. For example, if your application has 1000 menu items, each one with a slow load-time, and your users are probably only using 10 of those 1000 menu items, then it is OK.
But what happens when the user does load those 1000 items? They will be kept in memory forever. So, the user will need to wait 1000 times and then those will be there forever (so new requests will not wait).
But if those 1000 items consume too much memory, I'll say that you should use
LazyAndWeak, after all the computer gets slower when running out-of-memory. If you think it is OK to keep all of them in memory, then use
BackgroundLoader as the user will wait for the program to load, maybe will wait for the first menu item to load... and if he keeps enough time there, when he uses any other item it will be already in memory, without the need to wait again.
In fact, for very large projects, I usually open the application, go out for a while and then return, expecting everything to be loaded. I consider it frustrating that, in Visual Studio, I end-up waiting again when I try to load an ASP.NET project for the first time or a WPF one.
I know CodeProject is for code... but should I really show the code here? I think explaining the basic idea is enough. So, I will only explain the idea.
LazyAndWeak uses a lock when creating the value (so two threads will not create duplicate values). When accessing the
Value, a call to
GCUtils.KeepAlive is made. I explained that class in one of my first articles, WeakReferences as a Good Caching Mechanism. The main problem of using
WeakReferences only is that items are discarded at every collection, even if they were recently used. This class forbids them to be collected at the next collection but allows them to be collected if a second collection happens and they are still unused.
BackgroundLoader is harder. A "loader" thread is created. Each time a
LazyLoader is created, it registers itself inside that thread and sets an event (
ManagedAutoResetEvent) so the loader thread starts to run. If 1000 items are registered, they will be loaded in order (there is not the risk of 1000 threads trying to load their items). This class also uses locking when accessing its
Value so if it is not loaded yet, it loads it immediately and removes this
BackgroundLoader instance from the
loader class, as it will be already loaded.
Also, there is the problem that you may create the background loader and then you discard it (in the sample project, when you change from one directory to another). That's why I implemented the
IDisposable interface. If the item is not loaded yet, it is in the list of "to load" items. If you discard it, it simply removes such item from the "to load" list.
How to Use the Classes
Using those classes is very similar to using the .NET
Lazy class. You simply create the
BackgroundLoader<SomeType> at the very first moment, probably with a declaration like this:
private readonly LazyAndWeak<SomeType> field = new LazyAndWeak<SomeType>();
And when you need to access the SomeType instance, you must access the
Value property of the created field.
It is important that SomeType has a default constructor or that you give a creator delegate to it, or you will get an exception. Also, as happens with
lazy, those helper objects are only useful to hold slow loading or memory-hungry objects. Don't use them for fast and small objects, as the helper objects themselves consume memory.
The attached sample is a very simple image visualizer. Its purpose is only to show the differences of the various techniques.
When you enter any directory, all image names are read and some type of lazy-loading will be done. When just opening or scrolling large folders, you will see red lines instead of images. Red lines means such images are not still in memory.
If you force collection, already loaded images will be reloaded using
LazyAndWeak (which shows it worked) but will not have any effects on
BackgroundLoader or custom
I forced a 100 milliseconds delay when loading images. That was on purpose to make the differences more visible as loading images is not that slow.
If you are using
BackgroundLoader, when you change directory, it will stop loading the old files. That's why
Dispose was needed. If I didn't do that, if I entered directory A, then B... B will only load after loading all (useless) A files.
- 1st December, 2011: Initial version