【问题标题】:Why would try/finally rather than a "using" statement help avoid a race condition?为什么尝试/最终而不是“使用”语句有助于避免竞争条件?
【发布时间】:2013-01-27 15:22:11
【问题描述】:

此问题与此处另一个帖子中的评论有关: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 是一个局部变量,如果代码在另一个线程上重新输入,则不会被共享,并且在同一个线程中,将它分配给它看起来非常安全(实际上等同于给定的代码)以通常的方式使用“使用”语句,而不是使用 try/finally 手动执行操作。谁能赐教?

【问题讨论】:

  • 你应该问原作者。这对我来说毫无意义。
  • 你为什么不问问那个代码的创建者? (FTFY)
  • 这是我的第一个想法,但很遗憾,我找不到联系他的方法,他的博客上没有电子邮件,也无法在此处或 MSDN 论坛上发送个人消息,我可以揣摩(但如果我很昏暗并错过了它,请指出我正确的方向!)

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


【解决方案1】:

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

来自MSDN

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

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

      【解决方案3】:

      是的,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 语句具有额外的安全措施以确保不会发生意外,它确保即使在对象内部重新分配对象时原始对象也会被释放使用语句。添加 catch 子句也不太容易发生事故。 using 语句的存在是为了减少出现错误的可能性,请始终使用它。


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

      lock (obj) {
          // statements
      }
      

      翻译为:

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

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

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

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

      【讨论】:

      • 感谢 Hans(也感谢其他所有人的回复)。因此,这是一个非病态的情况,但如果准确地说,它确实符合竞争条件。重新使用 Thread.Abort(),我完全同意最好避免这种情况,但在这种情况下确实没有其他选择,Entity Framework 4.x 不支持中断查询,我与 OP 的问题相同其他问题,因为如果用户执行其他操作,我会触发查询以响应需要提前中断的用户操作。
      • 我将不得不遵从关于使用 EF 的常识。但是您的评论中有一个巨大的危险信号。 “不支持中断查询”只是说“不支持线程中止”的好方法。如果 EF 确实支持线程中断,那么添加中断查询的方法就不会成为问题。并且会被添加到 api 中,这显然是一个可取的特性。你肯定是在玩火。
      • 是的,他们已经在 EF 6.x 中添加了它,但那是测试版,不支持我们正在使用的 Oracle 客户端库,所以我现在被这个 hack 所困扰。当我说“中断查询”时,我是不准确的——我真正的意思是没有适当的异步/任务式取消,所以如果你不愿意让查询运行完成,即使在结果变得无关紧要。
      • “当比赛发生时,实际上并没有发生根本性的错误,不需要处理对象。”有一次我不同意这种说法是构造函数是否要分配或打开一些外部资源,如池化数据库连接或类似的。在这种情况下,池中可供其他线程使用的连接将减少一个。这样做足够多次,您将不再能够获得与数据库的连接。不过,总的来说,我不会担心。
      【解决方案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 块之间的线程中止)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-01-30
        • 2010-09-25
        • 2017-12-02
        • 2019-01-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多