为什么 try/finally 而不是“using”语句有助于避免竞争条件?

2023-12-22

这个问题与另一篇文章中的评论相关:取消实体框架查询 https://stackoverflow.com/questions/5143957/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();

我无法理解这样的评论

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

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


是的,有可能有一场比赛using陈述。 C# 编译器转换

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

to:

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

当线程在以下时间之间中止时,就会发生竞争:obj赋值语句和 try 块。几率极小,但不为零。发生这种情况时,该对象不会被处置。请注意他如何通过将赋值移动到 try 块内来重写该代码,以便不会发生这种竞争。当比赛发生时,实际上没有什么根本性的错误,不需要处理物体。

必须在使线程中止稍微提高效率和编写之间做出选择using手动声明时,您应该首先选择不养成使用 Thread.Abort() 的习惯。我不建议实际这样做,using语句有额外的安全措施来确保意外不会发生,它确保即使在 using 语句内重新分配对象时原始对象也会被释放。添加 catch 子句也不太容易发生事故。这using声明存在于reduce出现错误的可能性,请始终使用它。


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

lock (obj) {
    // statements
}

翻译为:

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

完全相同的场景,线程中止可以在 Enter() 调用之后和进入 try 块之前发生。这会阻止进行 Exit() 调用。这是way当然,这比不进行的 Dispose() 调用更糟糕,这几乎肯定会导致死锁。该问题特定于 x64 抖动,详细描述如下乔·达菲博客文章 https://web.archive.org/web/20120611083524/http://www.bluebytesoftware.com/blog/2007/01/30/MonitorEnterThreadAbortsAndOrphanedLocks.aspx.

可靠地修复这个问题非常困难,将 Enter() 调用移到 try 块内并不能解决问题。您无法确定是否进行了 Enter 调用,因此无法可靠地调用 Exit() 方法而不可能触发异常。 Duffy 所说的 Monitor.ReliableEnter() 方法最终确实发生了。 .NET 4 版本的 Monitor 具有 TryEnter() 重载,该重载需要ref bool lockTaken争论。现在您知道可以调用 Exit() 了。

好吧,当你不注意的时候,可怕的东西会在晚上突然出现。编写可安全中断的代码是hard。明智的做法是永远不要假设您没有编写的代码已经解决了所有这些问题。测试这样的代码非常困难,因为竞争非常罕见。你永远无法确定。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

为什么 try/finally 而不是“using”语句有助于避免竞争条件? 的相关文章

随机推荐