所以我的问题基本上归结为,如果我返回一个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,你仍然应该返回一个任务。
我的直觉本能是错的吗?或者也许任务开销太小而无关紧要?或者这是一种“依赖于”的答案?
答案 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。