-1

设想

我要下载资源。我不希望资源被多次下载。如果线程a下载资源1,则应将其缓存,如果线程b1尝试同时下载资源,则应等待并使用缓存的资源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();
        }
    }
}

问题

它有效吗?有什么问题吗?

4

1 回答 1

1

C# 现在包含LazyConcurrent CollectionsMemoryCache - 为 MemoryCache 添加对 System.Runtime.Caching 的引用。

这就是我要做的 - 不需要额外的锁定,并且 Lazy 实现会处理竞争条件。

/// <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;
    }
}
于 2013-02-19T00:00:52.897 回答