2013-02-18 31 views
-1

場景

我想下載資源。我不希望資源被更多地下載一次。如果線程a下載資源1它應該被緩存,並且線程b應該等待並使用緩存資源1如果它嘗試同時下載資源1。如果線程c想要下載資源2,則不應該受線程ab的影響。有條件的線程鎖定

嘗試

我試圖實現下面的場景:

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(); 
     } 
    } 
} 

問題

是否行得通?任何問題?

+1

你試過了嗎? – PinnyM 2013-02-18 23:21:58

+0

是的,我認爲它有效。但我對多線程很陌生。 – knut 2013-02-18 23:23:21

+0

我可能會建議使用字典的SyncRoot,而不是創建自己的鎖。 GetLock將使用(((IDictionary)Locks).SyncRoot)和FetchResouce將使用(((IDictionary)Resources).SyncRoot) – 2013-02-18 23:33:27

回答

1

C#現在包含Lazy,Concurrent CollectionsMemoryCache - 爲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; 
    } 
}