所有MVC页面都失败,消息中已添加了具有相同键的项目

时间:2013-03-18 05:09:02

标签: asp.net asp.net-mvc-3 asp.net-mvc-4

最终,我正在努力解决Loading any MVC page fails with the error “An item with the same key has already been added.”An item with the same key has already been added中引用的同一问题。第一个链接的副本是All MVC pages fail with the message an item with the same key has already been added,但它有一些额外的相关信息,确认它只影响MVC页面,而应用appSettings的webforms和应用程序的其他方面继续正常工作。

我现在已经在生产中看到了这四次,并且在任何其他环境中都没有看到它(开发,测试,UAT)。我通过System.Web.WebPages的源代码和MVC堆栈的相关部分仔细检查和调试,但没有碰到任何突出的东西。通过从MVC3迁移到MVC4,此问题一直存在,而aspnetwebstack.codeplex.com的最新变更集似乎无法解决此问题。

问题的快速摘要:

  • 每个MVC页面都受到影响且完全无法使用
  • 使用appSettings的WebForms和应用程序的其他方面继续正常工作
  • 只有appPool重启才能解决此问题
  • 至少一次并且在上面的文章中引用,这是在IIS的appPool定期时间间隔回收之后发生的
  • 这种情况发生在低流量和高流量时段
  • 这发生在多个生产服务器上,但该问题仅在任何给定时间影响单个服务器,因为其他服务器继续提供MVC页面

有问题的代码行是var items = new Lazy<Dictionary<object, object>>(() => appSettings.AllKeys.ToDictionary(key => key, key => (object)appSettings[key], comparer));,但是当通过从items请求值强制延迟初始化时会发生这种情况.appSettings变量来自System.Web.WebConfigurationManager.AppSettings,这是一个直接的对System.Configuration.ConfigurationManager.AppSettings的静态引用。所以我相信这条线相当于:var items = new Lazy<Dictionary<object, object>>(() => System.Configuration.ConfigurationManager.AppSettings.AllKeys.ToDictionary(key => key, key => (object)appSettings[key], comparer));

我很少怀疑框架问题,但似乎appSettings有两个不同的密钥是相同的(与支持同一密钥的多个值的NameValueCollection不同)。 MVC堆栈中使用的comparer是StringComparer.OrdinalIgnoreCase,它似乎与配置系统使用的匹配。如果这是一个框架问题,当使用ToDictionary()扩展方法强制NameValueColleciton进入字典时,MVC堆栈似乎非常无情。我相信使用appSettings.AllKeys.Distinct().ToDictionary(...)可能会允许MVC堆栈正常运行,就像应用程序的其余部分一样,并且不知道重复键的可能性。这种无情的性质似乎也导致了NullReferenceException in WebConfigScopeDictionary

中描述的问题
Server stack trace: 
   at System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)
   at System.Linq.Enumerable.ToDictionary[TSource,TKey,TElement](IEnumerable`1 source, Func`2 keySelector, Func`2 elementSelector, IEqualityComparer`1 comparer)
   at System.Web.WebPages.Scope.WebConfigScopeDictionary.<>c__DisplayClass4.<.ctor>b__0()
   at System.Lazy`1.CreateValue()
Exception rethrown at [0]: 
   at System.Lazy`1.get_Value()
   at System.Web.WebPages.Scope.WebConfigScopeDictionary.TryGetValue(Object key, Object& value)
   at System.Web.Mvc.ViewContext.ScopeGet[TValue](IDictionary`2 scope, String name, TValue defaultValue)
   at System.Web.Mvc.ViewContext.ScopeCache..ctor(IDictionary`2 scope)
   at System.Web.Mvc.ViewContext.ScopeCache.Get(IDictionary`2 scope, HttpContextBase httpContext)
   at System.Web.Mvc.ViewContext.GetClientValidationEnabled(IDictionary`2 scope, HttpContextBase httpContext)
   at System.Web.Mvc.Html.FormExtensions.FormHelper(HtmlHelper htmlHelper, String formAction, FormMethod method, IDictionary`2 htmlAttributes)
   at ASP._Page_Areas_Client_Views_Equipment_Index_cshtml.Execute()
   at System.Web.WebPages.WebPageBase.ExecutePageHierarchy()
   at System.Web.Mvc.WebViewPage.ExecutePageHierarchy()
   at System.Web.WebPages.WebPageBase.ExecutePageHierarchy(WebPageContext pageContext, TextWriter writer, WebPageRenderingBase startPage)
   at System.Web.Mvc.ViewResultBase.ExecuteResult(ControllerContext context)
   at System.Web.Mvc.ControllerActionInvoker.<>c__DisplayClass1a.<InvokeActionResultWithFilters>b__17()
   at System.Web.Mvc.ControllerActionInvoker.InvokeActionResultFilter(IResultFilter filter, ResultExecutingContext preContext, Func`1 continuation)
   at System.Web.Mvc.ControllerActionInvoker.InvokeActionResultWithFilters(ControllerContext controllerContext, IList`1 filters, ActionResult actionResult)
   at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass25.<BeginInvokeAction>b__22(IAsyncResult asyncResult)
   at System.Web.Mvc.Controller.<>c__DisplayClass1d.<BeginExecuteCore>b__18(IAsyncResult asyncResult)
   at System.Web.Mvc.Async.AsyncResultWrapper.<>c__DisplayClass4.<MakeVoidDelegate>b__3(IAsyncResult ar)
   at System.Web.Mvc.Controller.EndExecuteCore(IAsyncResult asyncResult)
   at System.Web.Mvc.Async.AsyncResultWrapper.<>c__DisplayClass4.<MakeVoidDelegate>b__3(IAsyncResult ar)
   at System.Web.Mvc.MvcHandler.<>c__DisplayClass8.<BeginProcessRequest>b__3(IAsyncResult asyncResult)
   at System.Web.Mvc.Async.AsyncResultWrapper.<>c__DisplayClass4.<MakeVoidDelegate>b__3(IAsyncResult ar)
   at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
   at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)

要将我的问题与已经提出的问题分开,我会问有没有人看到配置系统被多个重复键损坏或者NameValueCollection.AllKeys返回两个相同的键?我知道您可以在配置文件中定义多个键,但最后一个键获胜,并且该场景不会重现此问题。

虽然我并不是一个人多次看到这种行为,但是相对较少的帖子描述了这个问题,所以我也怀疑它可能是一个配置/环境问题,但同样,服务器将运行数月而不会遇到这个问题,appPool重启立即纠正问题。

如果服务器开始看到此错误,我已通过强制重启appPool来解决此问题,但管理层对此“hacky”解决方案不满意,因为某些用户仍会遇到错误。

帮助?!?!?

编辑:

这是一个人为的,可以重现场景的模糊测试,但无助于解决问题。它发生在约。 20%的测试运行。由于其他线程原因,代码会爆炸,但是感兴趣的是“已经添加了相同的密钥”错误。

[TestClass]
public class UnitTest1
{
    readonly NameValueCollection _nameValueCollection = new NameValueCollection();
    private Lazy<Dictionary<object, object>> _items;

    [TestMethod]
    public void ReproduceSameKeyHasAlreadyBeenAdded()
    {
        Thread[] threads = new Thread[1000];
        for (int i = 0; i < 1000; i++)
        {
            ThreadStart threadStart = AddEntry;
            Thread thread = new Thread(threadStart);
            threads[i] = thread;
        }
        foreach (var thread in threads)
            thread.Start();
        Thread.Sleep(100);
        _items = new Lazy<Dictionary<object, object>>(() => _nameValueCollection.AllKeys.ToDictionary(key => key, key => (object)_nameValueCollection[key], ScopeStorageComparer.Instance));

        object value;
        _items.Value.TryGetValue("4", out value); // approx. 20% of time, blows up here with mentioned error
        Assert.IsTrue(value != null);
    }

    private int _counter;
    private void AddEntry()
    {
        _counter++;
        try
        { // add a bunch of even keys, every other one a duplicate
            _nameValueCollection.Add((_counter%2) == 0 ? _counter.ToString() : (_counter + 1).ToString(), "some value");
        }
        catch {}
    }
}


StackTrace:
       at System.ThrowHelper.ThrowArgumentException(ExceptionResource resource)
       at System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)
       at System.Linq.Enumerable.ToDictionary[TSource,TKey,TElement](IEnumerable`1 source, Func`2 keySelector, Func`2 elementSelector, IEqualityComparer`1 comparer)
       at UnitTestProject1.ReproduceSameKeyHasAlreadyBeenAdded.<TestMethod1>b__0() in c:\Git\AspNetWebStack\aspnetwebstack\UnitTestProject1\UnitTest1.cs:line 37
       at System.Lazy`1.CreateValue()

2 个答案:

答案 0 :(得分:1)

我们遇到了同样的问题,最后将其跟踪到动态更新ConfigurationManager.AppSettings集合。我在这里发布了一个完整的答案:https://stackoverflow.com/a/17415830/2423407

答案 1 :(得分:0)

您是否肯定在正在初始化项目的行上发生错误,而不是正在使用项目添加到其他字典的行(静态我会假设)。

对我来说,最有可能出现这种情况的方法是,如果代码是并行执行的(由两个并发用户执行),第二个代码是执行导致异常。

作为一般的解决方法,我通常将所有配置参数的强类型类初始化为auto-start provider(并添加一些强类型类型的值检查,例如检查int是否为int,等)以避免运行时错误。

这具有在预热时加载这些功能的双重好处,而不是当用户想要信息(更好的响应性能)时,并且也可以保证线程安全性。

看看这是否无法解决您的问题。如果您不想这样做,我至少会尝试执行因您遇到多个威胁而失败的代码,因为我猜您将看到您的错误发生得相当可靠。