Xerx and I have been playing around with using Lazy<T>
for one-time, asynchronous initialisation of values, and we stumbled across the various threading options that Lazy<T>
supports. After puzzling over the documentation for a while I thought it was probably easier just to run a few examples.
Here’s the test case we’ll use to demonstrate these options:
The func used to initialise lazy value takes at least 100ms to run, and increments and returns a counter value i
. We then try and access the lazy value directly, and while the initialisation code is running, we also try and access it from an async Task<T>
. We’ll then output what each method returns, as well as the final value of the counter i
.
Default thread safety
The default thread safety mode when you call new Lazy<T>(func)
, or new Lazy<T>(func, true)
, is LazyThreadSafetyMode.ExecutionAndPublication
. Running in this mode ensures the initialisation code only runs once:
direct lazy value access
task running
getting lazy value
v: 0, task: 0, i: 1
Both the task and direct access agree on the initialised value of 0
, and the i
counter has only incremented once to give 1
.
Safe on publication
If we now update the safety mode to LazyThreadSafetyMode.PublicationOnly
, we get this:
direct lazy value access
task running
getting lazy value
getting lazy value
v: 0, task: 0, i: 2
Here we can see the initialisation code has run twice; there are two "getting lazy value"
messages, and i
has been incremented twice to 2
. Both the task and direct access agree on the value 0
though. So the first initialisation value returned wins.
All bets are off
Finally, LazyThreadSafetyMode.None
(which is what we get when we call new Lazy<T>(func, false)
) gives this output:
direct lazy value access
task running
getting lazy value
System.AggregateException : One or more errors occurred.
----> System.InvalidOperationException : ValueFactory attempted to access the Value property of this instance.
This option provides no thread safety, so trying to access or set the value concurrently fails with an exception.
Summary
These options are explained in more detail on MSDN, but a quick summary is:
ExecutionAndPublication
ensures initialisation code is only executed once. This is the default.PublicationOnly
lets multiple threads race to initialise the value, and the first one finished wins.None
has no thread safety, and can throw exceptions on concurrent initialisation.