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

此问题涉及另一个post中的评论: 取消entity framework查询

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

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

我无法理解所说的评论

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

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

是的, 使用声明中可能存在竞争。 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()方法而不会触发exception。 Duffy谈论的Monitor.ReliableEnter()方法最终确实发生了。 .NET 4版本的Monitor有一个TryEnter()重载,它带有一个ref bool lockTaken参数。 现在您知道可以调用Exit()。

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

非常奇怪,因为using只是语法糖的尝试 – 最后块。

来自MSDN :

您可以通过将对象放在try块中然后在finally块中调用Dispose来实现相同的结果; 实际上,这就是编译器如何翻译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块中,但是对于可能发生的竞争条件没有影响(除了在如上所述的assignement和try块之间的线程中止)

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

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