C#++运算符在foreach循环中是否变为线程安全?

时间:2015-04-04 17:52:06

标签: c# foreach thread-safety increment interlocked-increment

最近我从VB转到C#,所以我经常使用C#到VB.NET转换器来理解语法差异。 在将下一个方法移到VB时,我发现了一件有趣的事情。

C#原始代码:

 public bool ExceedsThreshold(int threshold, IEnumerable<bool> bools)
{
   int trueCnt = 0;
   foreach(bool b in bools)
      if (b && (++trueCnt > threshold)) 
          return true;
   return false;          
} 

VB.NET结果:

Public Function ExceedsThreshold(threshold As Integer, bools As IEnumerable(Of Boolean)) As Boolean
Dim trueCnt As Integer = 0
For Each b As Boolean In bools
    If b AndAlso (System.Threading.Interlocked.Increment(trueCnt) > threshold) Then
        Return True
    End If
Next
Return False End Function

C#&#39; ++运算符替换为System.Threading.Interlocked.Increment 这是否意味着如果在++循环中使用线程安全foreach运算符,则不会成为线程安全的?它是一种语法糖吗?如果这是真的,那么为什么转换器将Interlocked.Increment置于VB版本中?我认为C#和VB中的foreach完全相同。或者它只是一个转换器&#34;保险&#34;?

3 个答案:

答案 0 :(得分:11)

我确信它只是一个转换器黑客,我想我可以解释这背后的原因。

但首先,回答您的问题,C#中的内置++运算符不是线程安全的。它只是以下过程的语法糖(在++i的情况下):

  • 读取i
  • 的值
  • 增加它
  • 将其写回i
  • 返回递增的值

由于存在单独的读写操作,因此这是非原子操作。

现在,在VB中没有直接等效的++运算符。最接近的是:

i += 1

这是声明。相比之下,++i表达式。您可以在另一个语句或表达式中使用++i,但不能使用VB语句。

使用Interlocked.Increment只是一种轻松翻译代码的聪明方法,而不必将整个语句分解为多个其他语句。

如果没有这个技巧,转换器就必须像这样分解表达式:

if (b && (++trueCnt > threshold))
    ...
If b Then
    trueCnt += 1
    If trueCnt > threshold Then
        ...
    End If
End If

正如您所看到的,需要更多的重写。如果trueCnt是属性,则甚至需要引入单独的临时变量(以避免对其进行两次评估)。

这需要比转换器使用的更简单的语法转换更深入的语义分析和控制流重写 - 只是因为trueCnt += 1无法在内使用用VB表达。

答案 1 :(得分:4)

我相信它是因为你想要在同一个语句中增加值来进行比较。我没有太多的理由,但它肯定是正确的答案,因为trueCnt + = 1不允许在同一行进行比较。它当然与foreach无关,尝试在循环外添加相同的行,我几乎可以肯定它也将转换为Increment。 VB .Net中没有其他语法可以在同一行中递增和比较。

答案 2 :(得分:2)

除了上述问题之外,C#的默认行为遵循Java不幸的整数溢出行为。尽管有时包装整数溢出语义很有用,但是C#通常不会区分整数应该包装溢出的情况与不期望整数包装的情况,但程序员不认为溢出陷阱是值得的。这可能会混淆转换工作,因为VB.NET使陷阱行为比包装行为更容易和更快,而C#则相反。因此,为了速度的原因,使用未经检查的数学来翻译代码的逻辑方法是在VB.NET中使用普通的检查数学,而翻译需要包装行为的代码的逻辑方法是在VB.NET中使用包装整数方法。 Threading.Interlocked.IncrementThreading.Increment.Add方法使用包装整数行为,因此虽然从速度角度看它们不是最佳的,但它们很方便。