When dealing with singletons on Azure Functions, there are several considerations. One is that global state is shared among AF invocations. So, if a function is invoked once and then again later (soon enough that the host hasn't unloaded your code), then the initialization only happens once. Another consideration is that AF is perfectly free to start multiple AF invocations simultaneously - so any singletons need to be threadsafe (including their initialization).
This means you'll want to use Lazy<T>
/ AsyncLazy<T>
. However, bear in mind that AF (with these types) will preserve the singleton state (post-initialization) for your next invocation even if it fails. This can be a problem particularly with cloud computing because if there's a network (or configuration) error when your AF is starting up, you want the initialization to be retried on the next AF invocation.
In conclusion, you want to use Lazy<T>
/ AsyncLazy<T>
in such a way that is threadsafe and does not preserve failures.
With Lazy<T>
, this means you have to use the LazyThreadSafetyMode.PublicationOnly
flag and pass a function to the constructor (not just implicitly use the default constructor for T
). Note that this means you need to ensure your initialization function itself is threadsafe because it can be executed by multiple threads simultaneously.
With AsyncLazy<T>
, you have to use the AsyncLazyFlags.RetryOnFailure
flag. Since AsyncLazy<T>
is essentially a Lazy<Task<T>>
, the async initialization task is "shared" among all simultaneous callers, and then atomically replaced with a new Lazy<Task<T>>
instance if it fails. So the async initialization function does not need to be threadsafe.
Since getting this all right (especially for multiple singletons) is rather copy-and-pasteish, I abstracted this out for the AF project I'm working on:
It took a while to get to this point, but I'm pretty pleased at how it's turned out. Been meaning to blog about this, too...