public async Task AddOrGetExisting_InvalidatesCompletedResultsWhenExpirationOnCompletionSet() { string key = "key"; string value = "test"; //Configure so that tasks should be immediately removed from the cache the instant they complete. var expirationPolicy = new TaskCacheItemPolicy { ExpirationOnCompletion = true }; var valueFactoryStarted = new ManualResetEvent(false); var valueFactoryContinue = new ManualResetEvent(false); Func <Task <TestValue> > valueFactory = () => { return(Task.Factory.StartNew(() => { valueFactoryStarted.Set(); valueFactoryContinue.WaitOne(); return new TestValue(value); })); }; //First assert that the value is not yet in the cache Assert.False(_cache.Contains(key)); var cacheUserTask = _cache.AddOrGetExisting(key, valueFactory, expirationPolicy); // Wait until the value get from cache is in the middle of the value generation. // At this point, a Task that is running but not completed has been added to the cache. valueFactoryStarted.WaitOne(); // While the value generation is still running, confirm that it is present in the cache. Assert.True(_cache.Contains(key)); // Let value generation run to completion. valueFactoryContinue.Set(); await cacheUserTask; // Assert that the value has now been invalidated and removed from the cache Assert.False(_cache.Contains(key)); }
public async Task <T> AddOrGetExisting <T>(string key, Func <Task <T> > valueFactory, TaskCacheItemPolicy policy = default) { var asyncLazyValue = _cache.GetOrCreate(key, entry => { //Add a common expiration token which is used for clearing the cache entry.AddExpirationToken(new CancellationChangeToken(_cts.Token)); //Customize expiration as per the policy entry.AbsoluteExpiration = policy.AbsoluteExpiration; entry.AbsoluteExpirationRelativeToNow = policy.AbsoluteExpirationRelativeToNow; entry.SlidingExpiration = policy.SlidingExpiration; return(new AsyncLazy <T>( factory: () => !policy.ExpirationOnCompletion ? valueFactory() : valueFactory() .ContinueWith(task => { //Add an expiration token which triggers when the Task has completed (succeeded/failed/cancelled) entry.AddExpirationToken(new TaskChangeToken(task)); return task; }) .Unwrap() )); }); try { var result = await asyncLazyValue.ConfigureAwait(false); // The awaited Task has completed. Check that the task still is the same version // that the cache returns (i.e. the awaited task has not been invalidated during the await). if (_cache.TryGetValue(key, out var existingValue)) { if (existingValue != asyncLazyValue) { // The awaited value is no more the most recent one. // Get the most recent value with a recursive call. return(await AddOrGetExisting(key, valueFactory, policy).ConfigureAwait(false)); } } return(result); } catch (Exception) { // Task object for the given key failed with exception. Remove the task from the cache. _cache.Remove(key); // Re throw the exception to be handled by the caller. throw; } }