为什么尝试/最终而不是“使用”语句有助于避免竞争条件?

时间:2013-02-12 10:38:12

标签: c# .net entity-framework asynchronous race-condition

此问题与另一篇帖子中的评论有关:Cancelling an Entity Framework Query

为了清楚起见,我将从那里重现代码示例:

    var thread = new Thread((param) =>
    {
        var currentString = param as string;

        if (currentString == null)
        {
            // TODO OMG exception
            throw new Exception();
        }

        AdventureWorks2008R2Entities entities = null;
        try // Don't use using because it can cause race condition
        {
            entities = new AdventureWorks2008R2Entities();

            ObjectQuery<Person> query = entities.People
                .Include("Password")
                .Include("PersonPhone")
                .Include("EmailAddress")
                .Include("BusinessEntity")
                .Include("BusinessEntityContact");
            // Improves performance of readonly query where
            // objects do not have to be tracked by context
            // Edit: But it doesn't work for this query because of includes
            // query.MergeOption = MergeOption.NoTracking;

            foreach (var record in query 
                .Where(p => p.LastName.StartsWith(currentString)))
            {
                // TODO fill some buffer and invoke UI update
            }
        }
        finally
        {
            if (entities != null)
            {
                entities.Dispose();
            }
        }
    });

thread.Start("P");
// Just for test
Thread.Sleep(500);
thread.Abort();

我无法理解所说的评论

  

请勿使用,因为它会导致竞争状况

entities是一个局部变量,如果代码在另一个线程上重新输入,则不会被共享,并且在同一个线程中,它看起来非常安全(实际上相当于给定的代码)它以通常的方式在“using”语句中,而不是使用try / finally手动执行。谁能开导我?

4 个答案:

答案 0 :(得分:42)

是的,使用语句可能会出现争用。 C#编译器转换

using (var obj = new Foo()) {
    // statements
}

为:

var obj = new Foo();
try {
   // statements
}
finally {
   if (obj != null) obj.Dispose();
}

当线程在 obj 赋值语句和try块之间中​​止时,就会发生竞争。赔率极小但不是零。发生这种情况时,不会丢弃该对象。请注意他是如何通过在try块中移动赋值来重写该代码的,这样就不会发生这种竞争。当比赛发生时,实际上没有任何根本错误,处理对象不是必需的。

必须选择使线程中止稍微提高效率并手动编写使用语句,首先应选择不养成使用Thread.Abort()的习惯。我不能建议实际执行此操作,使用语句具有额外的安全措施以确保不会发生事故,它确保即使在对象内部重新分配对象时原始对象也会被丢弃使用声明。添加catch子句也不太容易发生事故。 使用语句存在于减少错误的可能性,并始终使用它。


关于这个问题的一点点,答案很流行,还有另一个常见的C#语句,它受到完全相同的竞争。它看起来像这样:

lock (obj) {
    // statements
}

翻译为:

Monitor.Enter(obj);
// <=== Eeeek!
try {
    // statements
}
finally {
    Monitor.Exit(obj);
}

完全相同的情况,线程中止可以在Enter()调用之后和进入try块之前触发。这会阻止进行Exit()调用。这是方式比Dispose()调用更糟糕的当然,这几乎肯定会导致死锁。该问题特定于x64抖动,肮脏的细节在此Joe Duffy blog post中有详细描述。

很难可靠地修复此问题,在try块内移动Enter()调用无法解决问题。您无法确定是否已进行Enter调用,因此无法可靠地调用Exit()方法而不会触发异常。 Duffy谈论的Monitor.ReliableEnter()方法最终确实发生了。 .NET 4版本的Monitor获得了一个带有ref bool lockTaken参数的TryEnter()重载。现在您知道可以调用Exit()。

嗯,当你不看的时候,可怕的东西会在夜晚出现BUMP。编写可安全中断的代码 hard 。你明智地永远不要假设你没有写的代码得到了所有这些照顾。由于比赛非常罕见,因此测试此类代码非常困难。你永远不能确定。

答案 1 :(得分:7)

非常奇怪,因为using只是try-finally阻止的语法糖。

来自MSDN

  

通过将对象置于try中,可以获得相同的结果   阻止然后在finally块中调用Dispose;事实上,这是   如何使用编译器翻译using语句。

答案 2 :(得分:4)

根据您使用using还是明确try/finally,您可以使用示例代码略微使用

    AdventureWorks2008R2Entities entities = null;
    try // Don't use using because it can cause race condition
    {
        entities = new AdventureWorks2008R2Entities();
        ...
    } finally {
    } 

用using语句替换它可能看起来像

   using(var entities = new AdventureWorks2008R2Entities()){
      ...
   }

该规范的§8.13将扩展为

    AdventureWorks2008R2Entities entities = new AdventureWorks2008R2Entities();
    try
    {
        ...
    } finally {
    } 

因此,唯一真正的区别在于赋值不在try / finally块中,但是对于可能发生的竞争条件没有任何影响(除了Hans还注意到的在assignement和try块之间的线程中止)

答案 3 :(得分:2)

该注释没有任何意义,因为编译器会将using语句转换为try / finally块。由于“实体”不会在范围之外使用,因此使用使用法规会更容易,因为这会自动处理资源。

您可以在MSDN上阅读有关此内容的更多信息:using Statement (C# Reference)