25

这个问题与此处另一个帖子中的评论有关:取消实体框架查询

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

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

我无法理解上面所说的评论

不要使用 using,因为它会导致竞争条件

entities是一个局部变量,如果代码在另一个线程上重新输入,则不会被共享,并且在同一个线程中,在“使用”语句中分配它似乎非常安全(实际上等同于给定代码)通常的方式,而不是使用 try/finally 手动执行操作。任何人都可以启发我吗?

4

4 回答 4

44

是的,在using语句中可能存在竞争。C# 编译器转换

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

到:

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

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

必须在使线程中止稍微更有效率和手动编写语句之间做出选择,您应该首先选择不养成使用Thread.Abort() 的习惯。我不建议实际这样做,using语句具有额外的安全措施以确保不会发生意外,即使在 using 语句中重新分配对象时,它也确保原始对象得到处理。添加 catch 子句也不太容易发生事故。using语句的存在是为了减少错误的可能性,请始终使用它。


对这个问题稍加思考,答案很流行,还有另一个常见的 C# 语句遭受完全相同的比赛。它看起来像这样:

lock (obj) {
    // statements
}

翻译为:

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

完全相同的场景,线程中止可以在 Enter() 调用之后和进入 try 块之前触发。这可以防止进行 Exit() 调用。这比没有进行Dispose() 调用更糟糕,这几乎肯定会导致死锁。这个问题是特定于 x64 抖动的,这个Joe Duffy 博客文章中描述了肮脏的细节。

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

好吧,当你不看的时候,可怕的东西会在晚上发生碰撞。编写可安全中断的代码很难。永远不要认为不是您编写的代码已经处理了所有这些问题,这是明智的。测试这样的代码非常困难,因为比赛非常罕见。你永远无法确定。

于 2013-02-12T11:34:47.553 回答
7

很奇怪,原因using只是 try - finally 块的语法糖。

来自MSDN

您可以通过将对象放在 try 块中,然后在 finally 块中调用 Dispose 来获得相同的结果;事实上,这就是编译器翻译 using 语句的方式。

于 2013-02-12T10:50:10.197 回答
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 还指出的分配和 try 块之间的线程中止)

于 2013-02-12T11:50:15.007 回答
2

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

您可以在 MSDN 上阅读更多相关信息:using Statement (C# Reference)

于 2013-02-12T10:58:00.093 回答