if (HttpRuntime.Cache["c"] == null)
{
LockApp.Models.DBDataContext db = new Models.DBDataContext();
var master = db.Masters.ToList();
HttpRuntime.Cache.Add("c", master,
null, DateTime.Now.AddMonths(1),
TimeSpan.Zero, CacheItemPriority.Normal, null);
}
ViewBag.Data = (List<LockApp.Models.Master>)HttpRuntime.Cache["c"];
这里是遍历主对象和详细对象的 Razor 视图:
@foreach(var m in ViewBag.Data){
@m.Id<nbsp></nbsp>
foreach(var d in m.Details){
@d.Id<nbsp></nbsp>
}
<br />
}
它的功能非常完美,可以正确地缓存数据。然而,在清除缓存后有多个请求尝试访问网站时,它会失败 - 我正在使用JMeter进行测试,基本上是使用许多(50)并行线程击中网站,然后触摸web.config - 我立即开始看到以下两个错误之一:
索引超出了数组范围
在 foreach(var d in m.Details) 中出现此错误
这个错误永远不会消失,即缓存中的某些数据被损坏了
具有以下堆栈:
System.Collections.Generic.List`1.Add(T item) +34
System.Data.Linq.SqlClient.SqlConnectionManager.UseConnection(IConnectionUser user) +305
System.Data.Linq.SqlClient.SqlProvider.Execute(Expression query, QueryInfo queryInfo, IObjectReaderFactory factory, Object[] parentArgs, Object[] userArgs, ICompiledSubQuery[] subQueries, Object lastResult) +59
System.Data.Linq.SqlClient.SqlProvider.ExecuteAll(Expression query, QueryInfo[] queryInfos, IObjectReaderFactory factory, Object[] userArguments, ICompiledSubQuery[] subQueries) +118
System.Data.Linq.SqlClient.CompiledQuery.Execute(IProvider provider, Object[] arguments) +99
System.Data.Linq.DeferredSourceFactory`1.ExecuteKeyQuery(Object[] keyValues) +402
System.Data.Linq.DeferredSourceFactory`1.Execute(Object instance) +888
System.Data.Linq.DeferredSource.GetEnumerator() +51
System.Data.Linq.EntitySet`1.Load() +107
System.Data.Linq.EntitySet`1.GetEnumerator() +13
System.Data.Linq.EntitySet`1.System.Collections.IEnumerable.GetEnumerator() +4
ASP._Page_Views_Home_Index_cshtml.Execute() in c:\Users\prc0092\Documents\Visual Studio 2012\Projects\LockApp\LockApp\Views\Home\Index.cshtml:16
或者这个错误
ExecuteReader要求打开并可用的连接。连接的当前状态为打开。
在同一行foreach(var d in m.Details)
如果我停止使用并行请求访问该网站,此错误会在一段时间后消失
以下是堆栈跟踪:
System.Data.SqlClient.SqlConnection.GetOpenConnection(String method) +5316460
System.Data.SqlClient.SqlConnection.ValidateConnectionForExecute(String method, SqlCommand command) +7
System.Data.SqlClient.SqlCommand.ValidateCommand(String method, Boolean async) +155
System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, TaskCompletionSource`1 completion, Int32 timeout, Task& task, Boolean asyncWrite) +82
System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method) +53
System.Data.SqlClient.SqlCommand.ExecuteReader(CommandBehavior behavior, String method) +134
System.Data.SqlClient.SqlCommand.ExecuteDbDataReader(CommandBehavior behavior) +41
System.Data.Common.DbCommand.ExecuteReader() +12
System.Data.Linq.SqlClient.SqlProvider.Execute(Expression query, QueryInfo queryInfo, IObjectReaderFactory factory, Object[] parentArgs, Object[] userArgs, ICompiledSubQuery[] subQueries, Object lastResult) +1306
System.Data.Linq.SqlClient.SqlProvider.ExecuteAll(Expression query, QueryInfo[] queryInfos, IObjectReaderFactory factory, Object[] userArguments, ICompiledSubQuery[] subQueries) +118
System.Data.Linq.SqlClient.CompiledQuery.Execute(IProvider provider, Object[] arguments) +99
System.Data.Linq.DeferredSourceFactory`1.ExecuteKeyQuery(Object[] keyValues) +402
System.Data.Linq.DeferredSourceFactory`1.Execute(Object instance) +888
System.Data.Linq.DeferredSource.GetEnumerator() +51
System.Data.Linq.EntitySet`1.Load() +107
System.Data.Linq.EntitySet`1.GetEnumerator() +13
System.Data.Linq.EntitySet`1.System.Collections.IEnumerable.GetEnumerator() +4
ASP._Page_Views_Home_Index_cshtml.Execute() in c:\Users\prc0092\Documents\Visual Studio 2012\Projects\LockApp\LockApp\Views\Home\Index.cshtml:16
我尝试过的不同方法
双重锁定
没有帮助。
private static object ThisLock = new object();
public ActionResult Index()
{
if (HttpRuntime.Cache["c"] == null)
{
lock (ThisLock)
{
if (HttpRuntime.Cache["c"] == null)
{
预加载子数据
虽然有效,但需要不断维护,因为并非所有子数据都应该预加载。请参阅下一个注释。
DataLoadOptions dlo = new DataLoadOptions();
dlo.LoadWith<Master>(b => b.Details);
db.LoadOptions = dlo;
在尝试访问子对象时锁定主对象
同样需要维护,因为需要找到所有子对象被访问的初始位置 - 我们正在努力解决这个问题,因为进入网站的入口路径不同。
@foreach(var m in ViewBag.Data){
@m.Id<nbsp></nbsp>
lock (m){
foreach(var d in m.Details){
@d.Id<nbsp></nbsp>
}
}
<br />
}
切换到Entity Framework
在某些并行请求(核心i7上的50+)下,它仍然存在“打开连接”的问题,但比Linq-SQL好得多 - 正如我之前提到的那样,它会在一段时间后消失,而且我还没有看到数据损坏。
我们可能会完全切换到EF,因为这似乎是唯一可行的路径 - 假设不出现数据损坏 - 这需要在我的实际项目中进行测试。
虽然EF数据上下文也不是线程安全的,而且我认为EF数据对象会随着它们的上下文一起运行。 这可能是我尚未回答的唯一问题。
破解原因的理论
看起来将Linq-SQL对象存储在http缓存中会携带数据上下文。当该上下文稍后被多个线程用于访问子项时,会出现某种类型的并发问题,这表现为子对象的临时连接问题或完全数据损坏。由于无法断开/重新连接上下文与Linq对象,因此看起来唯一的建议是不要缓存需要延迟加载其子项的Linq对象 - 我所做的大量谷歌搜索似乎没有给出这个建议,事实上有时相反。
我已上传了完整的项目(适用于Visual Studio 2012和SQL Server 2012)
https://docs.google.com/file/d/0B8CQRA9dD8POb3U5RGtCV3BMeU0/edit?usp=sharing 以及一个简单的JMeter脚本,将使用并行请求击中您的本地机器: https://docs.google.com/file/d/0B8CQRA9dD8POd1VYdGRDMEFQbEU/edit?usp=sharing
要进行测试,请启动站点并运行测试 - 然后触摸站点上的web.config