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

Mik*_*nan 24 .net c# asynchronous entity-framework race-condition

此问题涉及另一个帖子中的评论:取消实体框架查询

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

    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();
Run Code Online (Sandbox Code Playgroud)

我无法理解所说的评论

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

entities是一个局部变量,如果代码在另一个线程上重新输入,则不会被共享,并且在同一个线程中,将它分配到"using"语句中是非常安全的(实际上等同于给定的代码)通常的方式,而不是手动使用try/finally.任何人都可以开导我吗?

Han*_*ant 42

是的,使用声明中可能存在竞争.C#编译器转换

using (var obj = new Foo()) {
    // statements
}
Run Code Online (Sandbox Code Playgroud)

至:

var obj = new Foo();
try {
   // statements
}
finally {
   if (obj != null) obj.Dispose();
}
Run Code Online (Sandbox Code Playgroud)

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

拥有了发展的线程之间进行选择中止稍微更高效和写作使用语句用手,你应该首先选择使用Thread.Abort的习惯没有得到().我不建议实际执行此操作,using语句具有额外的安全措施以确保不会发生事故,它确保即使在using语句中重新分配对象时原始对象也会被处置.添加catch子句也不太容易发生事故.该使用声明的存在是为了减少错误的可能性,可以经常使用.


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

lock (obj) {
    // statements
}
Run Code Online (Sandbox Code Playgroud)

翻译为:

Monitor.Enter(obj);
// <=== Eeeek!
try {
    // statements
}
finally {
    Monitor.Exit(obj);
}
Run Code Online (Sandbox Code Playgroud)

完全相同的情况,线程中止可以在Enter()调用之后和进入try块之前触发.这会阻止进行Exit()调用.这是比不发过程的Dispose()方法调用厉害,这几乎肯定会导致死锁.问题是x64抖动特有的,在这篇Joe Duffy博客文章中很好地描述了肮脏的细节.

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

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

  • 我将不得不遵循使用EF的常识.但是你的评论中有一个巨大的红旗."不支持中断查询"只是说"不支持线程中止"的好方法.如果EF实际上支持线程中断,那么添加一种中断查询的方法就不会有问题.并且会被添加到api中,这是一个明显可取的功能.你肯定在玩火. (2认同)

And*_*sev 7

非常奇怪,原因using只是尝试的语法糖 - 最后阻止.

来自MSDN:

您可以通过将对象放在try块中然后在finally块中调用Dispose来实现相同的结果; 实际上,这就是编译器如何翻译using语句.