【问题标题】:How can i use Lazy<T> in an ASP.NET MVC Controller?如何在 ASP.NET MVC 控制器中使用 Lazy<T>?
【发布时间】:2011-09-21 02:32:08
【问题描述】:

我有一个简单的ASP.NET MVC 控制器。在几个操作方法中,我访问了一个资源,我会说很昂贵

所以我想,为什么不让它成为静态的。因此,我认为我可以在 .NET 4.0 中利用 Lazy&lt;T&gt; 的使用,而不是使用 double checked locking。调用昂贵的服务一次而不是多次。

所以,如果这是我的伪代码,我该如何更改它使用Lazy&lt;T&gt;。 对于这个悔恨的例子,我将使用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);
    }
}

【问题讨论】:

  • 使用 ASP.NET 缓存有什么问题?
  • 听起来您正在寻找单例,而不是对象的惰性实例化。当然,您可以使用 Lazy 来创建单例...

标签: c# .net asp.net asp.net-mvc-3 lazy-loading


【解决方案1】:

在您的示例中,Directory.GetFiles 的结果取决于_foo 的值,这不是静态的。因此,您不能使用 Lazy&lt;string[]&gt; 的静态实例作为控制器所有实例之间的共享缓存。

ConcurrentDictionary&lt;TKey, TValue&gt; 听起来更接近您想要的。

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

【讨论】:

  • 这是个好主意!使用缓存选项(由 Martin 或 Chris 提供),可能会有多个请求尝试插入缓存 - 如果 - 请求同时发生(或多或少).. 对吗? (竞争条件排序的事情)而并发字典阻止第二、第三个请求(同时)执行昂贵的资源,对吧?
  • @Pure - 实际上不,如果多个线程在将值添加到缓存之前尝试获取值,ConcurrentDictionary 可能会多次调用“valueFactory”函数。 ConcurrentDictionary 是线程安全的(每个键只会添加一个值),但 valueFactory 的调用不会发生在锁内。
  • ConcurrentDictionary 是线程安全的,但是传递给 GetOrAdd 和 AddOrUpdate 的委托是在字典的内部锁之外调用的。更多信息:msdn.microsoft.com/en-us/library/dd997369.aspx
  • 所以如果你只是在你的工厂方法中创建一个Lazy&lt;T&gt;,你可以避免多次执行昂贵的操作。
【解决方案2】:

我同意 Greg 的观点,即 Lazy 在这里不合适。

您可以尝试使用 asp.net caching 来缓存文件夹的内容,使用 _foo.PathToFiles 作为您的密钥。这比 Lazy 有一个优势,您可以控制缓存的生命周期,因此它会每天或每周重新获取内容,而无需重新启动应用程序。

缓存对您的服务器也很友好,因为如果没有足够的内存来支持它,它会优雅地降级。

【讨论】:

  • +1 用于推荐内置的 ASP.NET 缓存。除非有充分的理由,否则不要重新发明轮子。
  • 好的,谢谢。我也喜欢你的内联缓存 - 可能不适用于这种情况,但可能对 Windows 窗体应用程序有用。
【解决方案3】:

Lazy&lt;T&gt; 在您不确定是否需要该资源时效果最佳,因此仅在实际需要时才及时加载。 无论如何,该操作总是会加载资源,但是因为它很昂贵,您可能希望将其缓存在某个地方?你可以试试这样的:

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

【讨论】:

    【解决方案4】:

    我刚遇到你描述的同样问题,所以我创建了一个类 CachedLazy&lt;T&gt; -> 允许在控制器实例之间共享值,但与 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;
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-07-11
      • 2019-09-27
      • 2018-06-18
      • 1970-01-01
      • 2021-08-13
      • 1970-01-01
      相关资源
      最近更新 更多