public void Set(
            TKey key,
            TValue value)
        {
            AsyncLazyWithRefreshTask <TValue> updateValue = new AsyncLazyWithRefreshTask <TValue>(value, this.cancellationTokenSource.Token);

            this.values.AddOrUpdate(
                key,
                updateValue,
                (key, originalValue) => updateValue);
        }
        /// <summary>
        /// <para>
        /// Gets value corresponding to <paramref name="key"/>.
        /// </para>
        /// <para>
        /// If another initialization function is already running, new initialization function will not be started.
        /// The result will be result of currently running initialization function.
        /// </para>
        /// <para>
        /// If previous initialization function is successfully completed it will return the value. It is possible this
        /// value is stale and will only be updated after the force refresh task is complete.
        /// </para>
        /// <para>
        /// Force refresh is true:
        /// If the key does not exist: It will create and await the new task
        /// If the key exists and the current task is still running: It will return the existing task
        /// If the key exists and the current task is already done: It will start a new task to get the updated values.
        ///     Once the refresh task is complete it will be returned to caller.
        ///     If it is a success the value in the cache will be updated. If the refresh task throws an exception the key will be removed from the cache.
        /// </para>
        /// <para>
        /// If previous initialization function failed - new one will be launched.
        /// </para>
        /// </summary>
        public async Task <TValue> GetAsync(
            TKey key,
            Func <Task <TValue> > singleValueInitFunc,
            bool forceRefresh,
            Action <TValue, TValue> callBackOnForceRefresh)
        {
            if (this.values.TryGetValue(key, out AsyncLazyWithRefreshTask <TValue> initialLazyValue))
            {
                if (!forceRefresh)
                {
                    return(await initialLazyValue.GetValueAsync());
                }

                return(await initialLazyValue.CreateAndWaitForBackgroundRefreshTaskAsync(
                           singleValueInitFunc,
                           () => this.TryRemove(key),
                           callBackOnForceRefresh));
            }

            // The AsyncLazyWithRefreshTask is lazy and won't create the task until GetValue is called.
            // It's possible multiple threads will call the GetOrAdd for the same key. The current asyncLazy may
            // not be used if another thread adds it first.
            AsyncLazyWithRefreshTask <TValue> asyncLazy = new AsyncLazyWithRefreshTask <TValue>(singleValueInitFunc, this.cancellationTokenSource.Token);
            AsyncLazyWithRefreshTask <TValue> result    = this.values.GetOrAdd(
                key,
                asyncLazy);

            // Another thread async lazy was inserted. Just await on the inserted lazy object.
            if (!object.ReferenceEquals(asyncLazy, result))
            {
                return(await result.GetValueAsync());
            }

            // This means the current caller async lazy was inserted into the concurrent dictionary.
            // The task is now awaited on so if an exception occurs it can be removed from
            // the concurrent dictionary.
            try
            {
                return(await result.GetValueAsync());
            }
            catch (Exception e)
            {
                DefaultTrace.TraceError(
                    "AsyncCacheNonBlocking Failed GetAsync with key: {0}, Exception: {1}",
                    key.ToString(),
                    e.ToString());

                // Remove the failed task from the dictionary so future requests can send other calls..
                this.values.TryRemove(key, out _);
                throw;
            }
        }
            public async Task <T> CreateAndWaitForBackgroundRefreshTaskAsync(
                Func <Task <T> > createRefreshTask,
                Action callbackOnRefreshFailure,
                Action <T, T> callBackOnForceRefresh)
            {
                this.cancellationToken.ThrowIfCancellationRequested();

                // The original task is still being created. Just return the original task.
                Task <T> valueSnapshot = this.value;

                if (AsyncLazyWithRefreshTask <T> .IsTaskRunning(valueSnapshot))
                {
                    return(await valueSnapshot);
                }

                // The above check handles the scenario where this value task is still processing.
                // It will only get here if the valueSnapshot is completed. This is needed for the
                // callback to compare the original value to the new value.
                T originalValue = default;

                if (valueSnapshot != null)
                {
                    originalValue = await valueSnapshot;
                }

                // Use a local reference to avoid it being updated between the check and the await
                Task <T> refresh = this.refreshInProgress;

                if (AsyncLazyWithRefreshTask <T> .IsTaskRunning(refresh))
                {
                    T result = await refresh;
                    callBackOnForceRefresh?.Invoke(originalValue, result);
                    return(result);
                }

                bool createdTask = false;

                lock (this.valueLock)
                {
                    if (AsyncLazyWithRefreshTask <T> .IsTaskRunning(this.refreshInProgress))
                    {
                        refresh = this.refreshInProgress;
                    }
                    else
                    {
                        createdTask            = true;
                        this.refreshInProgress = createRefreshTask();
                        refresh = this.refreshInProgress;
                    }
                }

                // Await outside the lock to prevent lock contention
                if (!createdTask)
                {
                    T result = await refresh;
                    callBackOnForceRefresh?.Invoke(originalValue, result);
                    return(result);
                }

                // It's possible multiple callers entered the method at the same time. The lock above ensures
                // only a single one will create the refresh task. If this caller created the task await for the
                // result and update the value.
                try
                {
                    T itemResult = await refresh;
                    lock (this)
                    {
                        this.value = Task.FromResult(itemResult);
                    }

                    callBackOnForceRefresh?.Invoke(originalValue, itemResult);
                    return(itemResult);
                }
                catch (Exception e)
                {
                    // faulted with exception
                    DefaultTrace.TraceError(
                        "AsyncLazyWithRefreshTask Failed with: {0}",
                        e.ToString());

                    callbackOnRefreshFailure();
                    throw;
                }
            }
        /// <summary>
        /// <para>
        /// Gets value corresponding to <paramref name="key"/>.
        /// </para>
        /// <para>
        /// If another initialization function is already running, new initialization function will not be started.
        /// The result will be result of currently running initialization function.
        /// </para>
        /// <para>
        /// If previous initialization function is successfully completed it will return the value. It is possible this
        /// value is stale and will only be updated after the force refresh task is complete.
        /// </para>
        /// <para>
        /// Force refresh is true:
        /// If the key does not exist: It will create and await the new task
        /// If the key exists and the current task is still running: It will return the existing task
        /// If the key exists and the current task is already done: It will start a new task to get the updated values.
        ///     Once the refresh task is complete it will be returned to caller.
        ///     If it is a success the value in the cache will be updated. If the refresh task throws an exception the key will be removed from the cache.
        /// </para>
        /// <para>
        /// If previous initialization function failed - new one will be launched.
        /// </para>
        /// </summary>
        public async Task <TValue> GetAsync(
            TKey key,
            Func <TValue, Task <TValue> > singleValueInitFunc,
            Func <TValue, bool> forceRefresh)
        {
            if (this.values.TryGetValue(key, out AsyncLazyWithRefreshTask <TValue> initialLazyValue))
            {
                try
                {
                    TValue cachedResult = await initialLazyValue.GetValueAsync();

                    if (forceRefresh == null || !forceRefresh(cachedResult))
                    {
                        return(cachedResult);
                    }
                }
                catch (Exception e)
                {
                    // This is needed for scenarios where the initial GetAsync was
                    // called but never awaited.
                    if (initialLazyValue.ShouldRemoveFromCacheThreadSafe())
                    {
                        bool removed = this.TryRemove(key);

                        DefaultTrace.TraceError(
                            "AsyncCacheNonBlocking Failed GetAsync. key: {0}, tryRemoved: {1}, Exception: {2}",
                            key,
                            removed,
                            e);
                    }

                    throw;
                }

                try
                {
                    return(await initialLazyValue.CreateAndWaitForBackgroundRefreshTaskAsync(
                               createRefreshTask : singleValueInitFunc));
                }
                catch (Exception e)
                {
                    if (initialLazyValue.ShouldRemoveFromCacheThreadSafe())
                    {
                        DefaultTrace.TraceError(
                            "AsyncCacheNonBlocking.GetAsync with ForceRefresh Failed. key: {0}, Exception: {1}",
                            key,
                            e);

                        // In some scenarios when a background failure occurs like a 404
                        // the initial cache value should be removed.
                        if (this.removeFromCacheOnBackgroundRefreshException(e))
                        {
                            this.TryRemove(key);
                        }
                    }

                    throw;
                }
            }

            // The AsyncLazyWithRefreshTask is lazy and won't create the task until GetValue is called.
            // It's possible multiple threads will call the GetOrAdd for the same key. The current asyncLazy may
            // not be used if another thread adds it first.
            AsyncLazyWithRefreshTask <TValue> asyncLazy = new AsyncLazyWithRefreshTask <TValue>(singleValueInitFunc, this.cancellationTokenSource.Token);
            AsyncLazyWithRefreshTask <TValue> result    = this.values.GetOrAdd(
                key,
                asyncLazy);

            // Another thread async lazy was inserted. Just await on the inserted lazy object.
            if (!object.ReferenceEquals(asyncLazy, result))
            {
                return(await result.GetValueAsync());
            }

            // This means the current caller async lazy was inserted into the concurrent dictionary.
            // The task is now awaited on so if an exception occurs it can be removed from
            // the concurrent dictionary.
            try
            {
                return(await result.GetValueAsync());
            }
            catch (Exception e)
            {
                DefaultTrace.TraceError(
                    "AsyncCacheNonBlocking Failed GetAsync with key: {0}, Exception: {1}",
                    key.ToString(),
                    e.ToString());

                // Remove the failed task from the dictionary so future requests can send other calls..
                this.values.TryRemove(key, out _);
                throw;
            }
        }