Contains a simple implementation of a lazy .Net Core compatible cache for awaitable cache items. Instead of already created/materialized objects, proper async factory operations are put into the cache. Those are awaited by subsequenty reads from the cache. The cache itself is build upon elements in System.Collections.Concurrent (I am using ConcurrentDictionary), System.Threading.Tasks (the lovely TPL) and Lazy value factory. Putting those powerful capabilities in combination, leads to a threadsafe lock free implementation.
Instances of the cache are created via constructor. The example below creates an instance whose items will per default reside for 5 seconds within the cache.
var defaultExpirationOf5Seconds = TimeSpan.FromMilliseconds(5000);
var myCacheInstance = new Cache<string>(defaultExpirationOf5Seconds)
You can encapsulate the creation of an item in the Cache by an asynchronous factory operation. Instead of putting the factory result to the cache, one can simply store corresponding factory method. The factory evaluates when a client requests/awaits the item from the cache. The operation is threadsafe, so that even when accessed concurrent by different threads, factory operation is only called once.
The following example reads the data for the string from a file, which is an IO operation typically implemented in asynchronous fashion. With GetOrCreate one needs not to take particualt care in concurrent scenarios. When multiple threads try to put an item under the same key to the dictionary, only one will make the race. All other clients will get the result of the winning thread.
var yieldItem = await myCacheInstance.GetOrCreateItem(
"thisIsAKey",
async () => await File.ReadAllTextAsync("C:\thisIsaFile.txt"));
It is also possible to overwrite the default expiration time of a cacheitem with encache operation.
var yieldItem = await myCacheInstance.GetOrCreateItem(
"thisIsAKey",
async () => await File.ReadAllTextAsync("C:\thisIsaFile.txt"),
TimeSpan.FromSeconds(4));
If you want to set an item straight forward into the cache you can use GetItem operation.
bool setItemResult = await myCacheInstance.SetItem(
"thisIsAKey",
async () => await File.ReadAllTextAsync("C:\thisIsaFile.txt"),
TimeSpan.FromSeconds(4));
Passing an items key to operation SetItem, emits the item from the cache.
string encachedSubsequently = await myCacheInstance.GetItem("thisIsAKey");
For removing items from cache, a TryRemove operation is in place. It emits a flag indicating whether the item could be removed or not. If and ony if returning true, a reference to the item can be fetched by an out parameter. In the latter case, the item already expired and the reference of the item points to the default instance of the cacheitem value (e.g. null, 0...).
LazyAwaitableCacheItem<string> removedInstance = null;
bool tryRemoveResult = cache.TryRemove("thisIsAKey", out removedInstance);
If you don't need a reference to the removed item, you can simply use a proper overload without the output parameter.
bool tryRemoveResult = cache.TryRemove("thisIsAKey");
It could always somehow happen that you factory operations raise an exception when those are awaited. The cache can handle this in several ways.
Aligned to Lazy value factory pattern a raised exception can be cached by and delivered to each subsequently requesting client. If you want to cache the exception as result form the factory until the cache item expires, you can use enum value AwaitCacheItemStrategyType.AwaitAndCachEachFactoryResult at construction.
Cache<string> cache = new Cache<string>(
AwaitCacheItemStrategyType.AwaitAndCachEachFactoryResult,
TimeSpan.FromSeconds(10));
In some cases you don't want to cache yield exceptions. Instead it could make more sense to retry factory method and caching its result only in case of flawless execution. For this purpose you can create cache instance per default or passing enum value AwaitCacheItemStrategyType.AwaitAndCacheOnlyOnFlawlessExecution to constructor.
Cache<string> cache = new Cache<string>(
AwaitCacheItemStrategyType.AwaitAndCacheOnlyOnFlawlessExecution,
TimeSpan.FromSeconds(10));
Planned extensions are:
- Create a nuget package.
- Supporting different strategies for item expiration. By know for each item a delayed task is launched, whose expiration triggers removement of item.
Let me know what could be helpful for you and I will give my best to adapt roadmap properly.
If you have an ideas or needs for changed, don't hesitate to contact me. I will also handle you pull requests.
If you need anything (questions, ideas...) please contact me under Andi.Kleinbichler@gmail.com.