当一个方法使用缓存但偶尔会进行I / O时,它应该返回T还是Task <t>?

时间:2016-03-15 01:32:58

标签: c# task

所以我的问题基本上归结为,如果我返回一个Task,那么调用者可以异步,即使99%的时间没有I / O?

说我的代码是这样的:

var countryList = _cacheService.Get<List<Country>>("Countries", LoadCountrys);

在此示例中, LoadCountrys 是一种进行I / O调用的方法。 因此,第一次调用它时,它会执行一些I / O来将所有国家/地区加载到缓存中,然后每次调用只会从内存缓存中读取(无I / O)。

方法签名如下:

List<Country> GetCountryList();

另一种方法是:

var countryList = await _cacheService.GetAsync<List<Country>>("Countries", LoadCountrysAsync);

方法签名如下:

List<Country> GetCountryListAsync();

在大多数情况下代码并非真正异步时,对我来说,支付任务开销似乎很浪费。

但是在Joe Duffy's blog中,在可变延迟和异步部分,他继续说,即使你很少做I / O,你仍然应该返回一个任务。

我的直觉本能是错的吗?或者也许任务开销太小而无关紧要?或者这是一种“依赖于”的答案?

2 个答案:

答案 0 :(得分:4)

处理此问题的最佳方法是返回Task<T>,但是您不需要制作快速路径&#34; async并招致overhad。只需使用Task.FromResult或调用第二个异步来实际执行工厂方法即可获取值。

public static class CacheManager
{
    public static Task<T> GetAsync<T>(string cacheKey, Func<Task<T>> factory)
where T : class
    {
        var result = (T)MemoryCache.Default.Get(cacheKey);
        if (result != null)
        {
            return Task.FromResult(result);
        }

        return RunFactory<T>(cacheKey, factory);
    }

    private static async Task<T> RunFactory<T>(string cacheKey, Func<Task<T>> factory)
        where T : class
    {
        await PurgeOldLocks();
        var cacheLock = _locks.GetOrAdd(cacheKey, (key) => new SemaphoreSlim(1));
        try
        {
            //Wait for anyone currently running the factory.
            await cacheLock.WaitAsync();

            //Check to see if another factory has already ran while we waited.
            var oldResult = (T)MemoryCache.Default.Get(cacheKey);
            if (oldResult != null)
            {
                return oldResult;
            }

            //Run the factory then cache the result.
            var newResult = await factory();
            MemoryCache.Default.Add(cacheKey, newResult, _myDefaultPolicy);
            return newResult;
        }
        finally
        {
            cacheLock.Release();
        }
    }

    private static async Task PurgeOldLocks()
    {
        try
        {
            //Only one thread can run the purge;
            await _purgeLock.WaitAsync();
            if ((DateTime.UtcNow - _lastPurge).Duration() > MinPurgeFrequency)
            {
                _lastPurge = DateTime.UtcNow;
                var locksSnapshot = _locks.ToList();
                foreach (var kvp in locksSnapshot)
                {
                    //Try to take the lock but do not wait for it.
                    var waited = await kvp.Value.WaitAsync(0);
                    if (waited)
                    {
                        //We where able to take the lock so remove it from the collection and dispose it.
                        SemaphoreSlim _;
                        _locks.TryRemove(kvp.Key, out _);
                        kvp.Value.Dispose();
                    }
                }
            }
        }
        finally
        {
            _purgeLock.Release();
        }
    }

    public static TimeSpan MinPurgeFrequency { get; set; } = TimeSpan.FromHours(1);
    private static DateTime _lastPurge = DateTime.MinValue;

    private static readonly SemaphoreSlim _purgeLock = new SemaphoreSlim(1);
    private static readonly ConcurrentDictionary<string, SemaphoreSlim> _locks = new ConcurrentDictionary<string, SemaphoreSlim>();

    private static CacheItemPolicy _myDefaultPolicy = //...
}

为了确保两个线程不同时运行工厂,我保留ConcurrentDictionary<string, SemaphoreSlim>来锁定工厂的运行。然而,这将导致内存泄漏,所以每小时我通过一个独占锁定列表并删除任何密钥,我可以锁定。

答案 1 :(得分:1)

  

我应该返回一个任务,所以调用者可以是异步的,即使是99%的   那时候,没有I / O?

我的回答是肯定的。从理论上讲,async方法可能稍慢,因为生成/执行了更多的代码(状态机)。但由于async brings more

,这一点可以忽略不计