Как я могу использовать Lazy <T>в ASP.NET MVC Controller? - PullRequest
6 голосов
/ 21 сентября 2011

У меня есть простой ASP.NET MVC контроллер. В нескольких методах действий я получаю доступ к ресурсу, который, как я скажу, стоит .

Вот я и подумал, почему бы не сделать это статичным Поэтому вместо двойной проверки блокировки я подумал, что могу использовать Lazy<T> в .NET 4.0. Позвоните в дорогую услугу один раз, а не несколько раз.

Итак, если это мой псевдо-код, как я могу его изменить, используйте Lazy<T>. В этом сокрушительном примере я буду использовать File System в качестве дорогого ресурса. Таким образом, в этом примере вместо получения всех файлов по пути назначения каждый раз, когда запрос вызывает этот ActionMethod, я надеялся использовать Lazy для хранения этого списка файлов ... что, конечно, делает вызов только в первый раз.

Следующее предположение: не беспокойтесь, если содержание изменилось. Это выходит за рамки, здесь.

public class FooController : Controller
{
    private readonly IFoo _foo;
    public FooController(IFoo foo)
    {
        _foo = foo;
    }

    public ActionResult PewPew()
    {
        // Grab all the files in a folder.
        // nb. _foo.PathToFiles = "/Content/Images/Harro"
        var files = Directory.GetFiles(Server.MapPath(_foo.PathToFiles));

        // Note: No, I wouldn't return all the files but a concerete view model
        //       with only the data from a File object, I require.
        return View(files);
    }
}

Ответы [ 4 ]

5 голосов
/ 21 сентября 2011

В вашем примере результат Directory.GetFiles зависит от значения _foo, которое не является статическим. Поэтому вы не можете использовать статический экземпляр Lazy<string[]> в качестве общего кэша между всеми экземплярами вашего контроллера.

ConcurrentDictionary<TKey, TValue> звучит как то, что ближе к тому, что вы хотите.

// Code not tested, blah blah blah...
public class FooController : Controller
{
    private static readonly ConcurrentDictionary<string, string[]> _cache
        = new ConcurrentDictionary<string, string[]>();

    private readonly IFoo _foo;
    public FooController(IFoo foo)
    {
        _foo = foo;
    }

    public ActionResult PewPew()
    {
        var files = _cache.GetOrAdd(Server.MapPath(_foo.PathToFiles), path => {
            return Directory.GetFiles(path);
        });

        return View(files);
    }
}
4 голосов
/ 21 сентября 2011

Я согласен с Грегом, что Ленивый <> здесь неуместен.

Вы можете попробовать использовать asp.net caching для кэширования содержимого папки, используя _foo.PathToFiles в качестве ключа. Это имеет преимущество перед Lazy <> в том, что вы можете контролировать время жизни кэширования, поэтому оно будет обновлять содержимое каждый день или каждую неделю, не требуя перезапуска приложения.

Кроме того, кеширование дружественно для вашего сервера в том смысле, что оно будет постепенно ухудшаться, если не будет достаточно памяти для его поддержки.

2 голосов
/ 21 сентября 2011

Lazy<T> работает лучше всего, когда вы не уверены, нужен ли вам ресурс, поэтому он загружается точно в срок, только когда он действительно необходим.Действие всегда будет загружать ресурс независимо от того, но потому что это дорого, вы, вероятно, хотите где-то его кэшировать?Вы можете попробовать что-то вроде этого:

public ActionResult PewPew()
{
    MyModel model;
    const string cacheKey = "resource";
    lock (controllerLock)
    {
        if (HttpRuntime.Cache[cacheKey] == null)
        {
            HttpRuntime.Cache.Insert(cacheKey, LoadExpensiveResource());
        }
        model = (MyModel) HttpRuntime.Cache[cacheKey];
    }

    return View(model);
}
1 голос
/ 07 декабря 2012

У меня была та же проблема, которую вы описали, поэтому я создал класс CachedLazy<T> ->, позволяющий разделять значения между экземплярами контроллера, но с дополнительным истечением по времени и однократным созданием, в отличие от ConcurrentDictionary.

/// <summary>
/// Provides a lazily initialised and HttpRuntime.Cache cached value.
/// </summary>
public class CachedLazy<T>
{
    private readonly Func<T> creator;

    /// <summary>
    /// Key value used to store the created value in HttpRuntime.Cache
    /// </summary>
    public string Key { get; private set; }

    /// <summary>
    /// Optional time span for expiration of the created value in HttpRuntime.Cache
    /// </summary>
    public TimeSpan? Expiry { get; private set; }

    /// <summary>
    /// Gets the lazily initialized or cached value of the current Cached instance.
    /// </summary>
    public T Value
    {
        get
        {
            var cache = HttpRuntime.Cache;

            var value = cache[Key];
            if (value == null)
            {
                lock (cache)
                {
                    // After acquiring lock, re-check that the value hasn't been created by another thread
                    value = cache[Key];
                    if (value == null)
                    {
                        value = creator();
                        if (Expiry.HasValue)
                            cache.Insert(Key, value, null, Cache.NoAbsoluteExpiration, Expiry.Value);
                        else
                            cache.Insert(Key, value);
                    }
                }
            }

            return (T)value;
        }
    }

    /// <summary>
    /// Initializes a new instance of the CachedLazy class. If lazy initialization occurs, the given
    /// function is used to get the value, which is then cached in the HttpRuntime.Cache for the 
    /// given time span.
    /// </summary>
    public CachedLazy(string key, Func<T> creator, TimeSpan? expiry = null)
    {
        this.Key = key;
        this.creator = creator;
        this.Expiry = expiry;
    }
}
...