为什么要尝试/最终而不是“使用"?语句有助于避免竞争条件? [英] Why would try/finally rather than a "using" statement help avoid a race condition?

查看:18
本文介绍了为什么要尝试/最终而不是“使用"?语句有助于避免竞争条件?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

此问题与此处另一篇文章中的评论有关:取消实体框架查询

This question relates to a comment in another posting here: Cancelling an Entity Framework Query

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

I will reproduce the code example from there for clarity:

    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,因为它会导致竞争条件

Don't use using because it can cause race condition

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

entities is a local variable and won't be shared if the code is re-entered on another thread, and within the same thread it would seem perfectly safe (and indeed equivalent to the given code) to assign it inside a "using" statement in the usual way, rather than doing things manually with the try/finally. Can anyone enlighten me?

推荐答案

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

Yeah, there is a possible race in the using statement. The C# compiler transforms

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

到:

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

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

The race occurs when the thread is aborted right between the obj assignment statement and the try block. Extremely small odds but not zero. The object won't be disposed when that happens. Note how he rewrote that code by moving the assignment inside the try block so this race cannot occur. Nothing actually goes fundamentally wrong when the race occurs, disposing objects is not a requirement.

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

Having to choose between making thread aborts marginally more efficient and writing using statements by hand, you should first opt for not getting in the habit of using Thread.Abort(). I can't recommend actually doing this, the using statement has additional safety measures to ensure accidents don't happen, it makes sure that the original object gets disposed even when the object is re-assigned inside the using statement. Adding catch clauses is less prone to accidents as well. The using statement exists to reduce the likelihood of bugs, always use it.

稍微讨论一下这个问题,答案很流行,还有另一个常见的 C# 语句也存在完全相同的问题.它看起来像这样:

Noodling on a bit about this problem, the answer is popular, there's another common C# statement that suffers from the exact same race. It looks like this:

lock (obj) {
    // statements
}

翻译为:

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

完全相同的场景,线程中止可以在 Enter() 调用之后和进入 try 块之前发生.这会阻止 Exit() 调用.这比当然没有进行的 Dispose() 调用方式更糟糕,这几乎肯定会导致死锁.该问题特定于 x64 抖动,在此 Joe Duffy 博文.

Exact same scenario, the thread abort can strike after the Enter() call and before entering the try block. Which prevents the Exit() call from being made. This is way nastier than a Dispose() call that isn't made of course, this is almost certainly going to cause deadlock. The problem is specific to the x64 jitter, the sordid details are described well in this Joe Duffy blog post.

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

It is very hard to reliably fix this one, moving the Enter() call inside the try block can't solve the problem. You cannot be sure that the Enter call was made so you cannot reliably call the Exit() method without possibly triggering an exception. The Monitor.ReliableEnter() method that Duffy was talking about did eventually happen. The .NET 4 version of Monitor got a TryEnter() overload that takes a ref bool lockTaken argument. Now you know it is okay to call Exit().

好吧,当你不看的时候,可怕的东西会在晚上变得颠簸.编写可安全中断的代码是困难.您永远不要认为不是您编写的代码会处理所有这些问题,这是明智之举.测试这样的代码非常困难,因为比赛非常罕见.你永远无法确定.

Well, scary stuff that goes BUMP in the night when you are not looking. Writing code that's safely interruptable is hard. You'd be wise to never assume that code that you didn't write got all of this taken care of. Testing such code is extremely difficult since the race is so rare. You can never be sure.

这篇关于为什么要尝试/最终而不是“使用"?语句有助于避免竞争条件?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆