在异步服务中缓存

时间:2015-04-09 14:54:18

标签: wcf caching asynchronous

所以,我遇到了一个有趣的情况。我们假设我的数据集很少变化,因此我不想在每次调用高容量服务时都能获得这些数据。我通常会将这些数据缓存一段时间(取决于它实际变化的程度)。这非常简单:

private T GetOrSet<T>(string key, Func<T> getToSet, int minutes, object lockObject)
{
    T value = (T)HttpRuntime.Cache.Get(key);
    if (value == null)
    {
        lock (lockObject)
        {
            value = (T)HttpRuntime.Cache.Get(key);
            if (value == null)
            {
                value = getToSet();
                HttpRuntime.Cache.Insert(key, value, null, DateTime.UtcNow.AddMinutes(minutes), Cache.NoSlidingExpiration);
            }
        }
    }
    return value;
}

如果getToSet函数失败,我从不插入,所以下次调用时会再次尝试。

但是,如果我在异步服务中使用相同的模式,getToSet函数将返回Task<> - 现在我已缓存Task,返回失败的结果一段时间。假设调用getToSet的时间不可忽略,如何在数据检索期间仍然没有阻塞线程时阻止缓存失败的结果?

下面简短而完整的例子;这会将失败的结果缓存一分钟,在此期间每次对GetData的调用都将失败,然后服务将开始为每次调用返回5

using System;
using System.ServiceModel;
using System.Threading.Tasks;
using System.Web;
using System.Web.Caching;

namespace TaskCacheIssue
{
    [ServiceContract]
    public interface IService1
    {
        [OperationContract]
        Task<int> GetData();
    }

    public class Service1 : IService1
    {

        public async Task<int> GetData()
        {
            return await GetDataFromCache();
        }

        private static bool first = true;
        private static readonly object deadbolt = new object();
        private async Task<int> GetDataFromCache()
        {
            return await GetOrSet(
                "key",
                async () => await GetDataFromSomewhereElse(),
                1,
                deadbolt);
        }

        private async Task<int> GetDataFromSomewhereElse()
        {
            // This is actually a longer-running data retrieval.
            if (first)
            {
                first = false;
                throw new Exception("FIRST!");
            }
            return 5;
        }

        private T GetOrSet<T>(string key, Func<T> getToSet, int minutes, object lockObject)
        {
            T value = (T)HttpRuntime.Cache.Get(key);
            if (value == null)
            {
                lock (lockObject)
                {
                    value = (T)HttpRuntime.Cache.Get(key);
                    if (value == null)
                    {
                        value = getToSet();
                        HttpRuntime.Cache.Insert(key, value, null, DateTime.UtcNow.AddMinutes(minutes), Cache.NoSlidingExpiration);
                    }
                }
            }
            return value;
        }

    }
}

2 个答案:

答案 0 :(得分:1)

从缓存中检索值并等待它检查结果时。如果失败,请清除缓存并在其中插入新值。

答案 1 :(得分:1)

除了专门评估null之外,还要检查缓存的值是否为失败的任务。

private T GetOrSet<T>(string key, Func<T> getToSet, int minutes, object lockObject)
{
    T value = (T)HttpRuntime.Cache.Get(key);
    if (value == null || IsCanceledOrFaultedTask(value))
    {
        lock (lockObject)
        {
            value = (T)HttpRuntime.Cache.Get(key);
            if (value == null || IsCanceledOrFaultedTask(value))
            {
                value = getToSet();
                HttpRuntime.Cache.Insert(key, value, null, DateTime.UtcNow.AddMinutes(minutes), Cache.NoSlidingExpiration);
            }
        }
    }
    return value;
}

private bool IsCanceledOrFaultedTask(object target)
{
    if (target is Task)
    {
        var task = (Task)target;
        return (task.IsCanceled || task.IsFaulted);
    }

    return false;
}