我的EF 4.1有问题。我们在一个方法中发现了项目中的性能问题,我开始使用dotTrace来解决问题,并且我发现了这样的奇怪行为:
当应用程序启动时 - 一切正常,首先调用该方法工作正常,但稍后我每次在站点上执行某些操作时,Monitor.Enter上花费的时间都会增加。
我知道问题在于锁定,这意味着有些东西阻止了调用,但我不知道那里发生了什么。如果我有EF 4.1的来源,我可以尝试调试整个事情,但这是我要做的最后一件事。 你能不能给我一些提示,哪些可能影响这种行为?我认为第一件事是交易,但我找不到这个项目中使用的任何交易,至少是明确的! 欢迎您写下您的任何想法,因为我在互联网上找不到任何类似的问题。
谢谢。
答案 0 :(得分:2)
这是一个非常有趣的问题。我只是在Reflector .NET的帮助下进行了小的快速分析(你也可以使用ILSpy,JustDecompile或DotPeek来查看任何未被混淆的.NET源代码)。分析可能不正确或基于不正确的假设,因此请将其考虑在内。
让我们从InitializeDatabaseAction
开始:
private void InitializeDatabaseAction(Action<InternalContext> action)
{
Func<Tuple<DbCompiledModel, string>, RetryAction<InternalContext>> valueFactory = null;
if (!this._inDatabaseInitialization)
{
try
{
this._inDatabaseInitialization = true;
if (valueFactory == null)
{
// Delegate to create a new instance of RetryAction
valueFactory = t => new RetryAction<InternalContext>(action);
}
// InitializeDatabases is ConcurrentDictionary - it stores information
// about all compiled models and they related database connection strings
// This call will try to get existing RetryAction for the model and if
// it doesn't exists it will use current valueFactory to create
// a new instance and add it to dictionary. It will also return
// that value and execute its PerformAction operation.
// If you have just one context and one database you will have only
// single record in the concurrent dictionary but every creation
// of your DbContext will go through this call to ensure that database
// is initialized. This code is executed when your context is used
// for data retrieval or persistence for the first time.
InitializedDatabases.GetOrAdd(Tuple.Create<DbCompiledModel, string>(this._model, this._internalConnection.ConnectionKey), valueFactory).PerformAction(this);
}
finally
{
this._inDatabaseInitialization = false;
}
}
}
现在让我们检查一下RetryAction
课程:
/// <summary>
/// Adapted from Lazy<> to allow the initializer to take an input object and
/// to do one-time initialization that only has side-effects and doesn't
/// return a value.
/// </summary>
internal class RetryAction<TInput>
{
// Fields
private Action<TInput> _action;
private readonly object _lock;
// Methods
public RetryAction(Action<TInput> action)
{
this._lock = new object();
this._action = action;
}
/// <summary>
/// Performs the action unless it has already been successfully
/// performed before.
/// </summary>
public void PerformAction(TInput input)
{
// Here we have Monitor.Enter
lock (this._lock)
{
if (this._action != null)
{
Action<TInput> action = this._action;
this._action = null;
try
{
action(input);
}
catch (Exception)
{
this._action = action;
throw;
}
}
}
}
}
如果您有许多并发线程(您的ASP.NET MVC应用程序负载很重)并且您正在创建大量DbContext实例,那么执行操作中的lock
可能确实是您的吞吐量问题。我相信这可以被视为一个错误,并且它具有非常简单的修复方法,可以在大多数情况下提高吞吐量:
public void PerformAction(TInput input)
{
// This is known as Double-Checked Locking
if (this._action != null)
{
lock (this._lock)
{
if (this._action != null)
{
...
}
}
}
}
跟踪中的第二个问题是同样的问题。
我建议你再次验证这是你的问题的真正根源(对于一些调用不应该是问题,因为锁争用是重负载的问题)并在MS Connect上打开bug或将其发布到ADO.NET team。您可以参考这篇文章作为问题的描述。
答案 1 :(得分:0)
我在代码中发现了一个问题。它一如既往地是对一切产生巨大影响的小事。
正如我已经说过的,在应用程序中存在一个奇怪的问题 - 每次调用MVC应用程序,因此对EntityFramework的调用都在单独的AppDomain中。 EF CodeFirst每个AppDomain构建和编译一次模型,在我的情况下,它每次都在构建和编译模型,因为它是一个单独的AppDomain。原因很简单,其他团队中的某个人已将文件系统日志记录添加到被调用的程序集中,并且对该程序集的类的每次调用都更改了文件,WebServer正在重新启动应用程序。我刚刚关闭了日志记录,它解决了问题。现在我没有这个性能问题。