我想下载资源。我不希望一次更多地下载资源。如果线程 a 下载资源1
,它应该被缓存,并且线程 b 应该等待并使用缓存资源1
如果它尝试下载资源同时1
。如果线程 c 想要下载资源2
,则它不应受线程 a 和 b 的影响。
我试图实现以下方案:
using System;
using System.Collections.Generic;
using System.Threading;
namespace ConsoleApplication1
{
class ConditionalThreadLockingProgram
{
private static readonly object _lockObject = new object();
private static readonly Dictionary<int, string> Locks =
new Dictionary<int, string>();
private static readonly Dictionary<int, string> Resources =
new Dictionary<int, string>();
public static string GetLock(int resourceId)
{
lock (_lockObject)
{
if (Locks.ContainsKey(resourceId))
{
return Locks[resourceId];
}
return Locks[resourceId] = string.Format(
"Lock #{0}",
resourceId
);
}
}
public static void FetchResource(object resourceIdObject)
{
var resourceId = (int)resourceIdObject;
var currentLock = GetLock(resourceId);
lock (currentLock)
{
if (Resources.ContainsKey(resourceId))
{
Console.WriteLine(
"Thread {0} got cached: {1}",
Thread.CurrentThread.Name,
Resources[resourceId]
);
return;
}
Thread.Sleep(2000);
Console.WriteLine(
"Thread {0} downloaded: {1}",
Thread.CurrentThread.Name,
Resources[resourceId] = string.Format(
"Resource #{0}",
resourceId
)
);
}
}
static void Main(string[] args)
{
new Thread(FetchResource) { Name = "a" }.Start(1);
new Thread(FetchResource) { Name = "b" }.Start(1);
new Thread(FetchResource) { Name = "c" }.Start(2);
Console.ReadLine();
}
}
}
有用吗?有什么问题吗?
答案 0 :(得分:1)
C#现在包含Lazy,Concurrent Collections和MemoryCache - 为MemoryCache添加对System.Runtime.Caching的引用。
这就是我要做的事情 - 不需要额外的锁定,懒惰的实现可以解决竞争条件。
/// <summary>
/// Summary description for ResourceFactory
/// </summary>
public static class ResourceFactory
{
private const string _cacheKeyFormat = "AppResource[{0}]";
private static readonly ObjectCache _cache = MemoryCache.Default;
private static readonly CacheItemPolicy _policy = new CacheItemPolicy()
{
SlidingExpiration = TimeSpan.FromMinutes(Int32.Parse(ConfigurationManager.AppSettings["AppResourceTimeout"] ?? "20")),
RemovedCallback = new CacheEntryRemovedCallback(AppResourceRemovedCallback)
};
private static void AppResourceRemovedCallback(CacheEntryRemovedArguments args)
{
// item was removed from cache
}
#region Extensions to make ObjectCache work with Lazy
public static TValue GetOrAdd<TKey, TValue>(this ObjectCache @this, TKey key, Func<TKey, TValue> valueFactory, CacheItemPolicy policy)
{
Lazy<TValue> lazy = new Lazy<TValue>(() => valueFactory(key), true);
return ((Lazy<TValue>)@this.AddOrGetExisting(key.ToString(), lazy, policy) ?? lazy).Value;
}
public static TValue GetOrAdd<TKey, TParam1, TValue>(this ObjectCache @this, TKey key, TParam1 param1, Func<TKey, TParam1, TValue> valueFactory, CacheItemPolicy policy)
{
Lazy<TValue> lazy = new Lazy<TValue>(() => valueFactory(key, param1), true);
return ((Lazy<TValue>)@this.AddOrGetExisting(key.ToString(), lazy, policy) ?? lazy).Value;
}
#endregion
public static AppResourceEntity GetResourceById(int resourceId)
{
#region sanity checks
if (resourceId < 0) throw new ArgumentException("Invalid parameter", "resourceId");
#endregion
string key = string.Format(_cacheKeyFormat, resourceId);
AppResourceEntity resource = _cache.GetOrAdd(
key,
resourceId,
(k, r) =>
{
return AppResourceDataLayer.GetResourceById(r);
},
_policy
);
return resource;
}
}