Становится ли оператор C# ++ потокобезопасным в цикле foreach?

Недавно я перешел с 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# 's ++ оператор заменен на System.Threading.Interlocked.IncrementЗначит ли это, что не потокобезопасен? ++ оператор становится потокобезопасным, если используется в foreach цикл? Это своего рода синтаксис сахара? Если это правда, то почему конвертер размещен Interlocked.Increment в версии VB? Я думал, что foreach в C# и VB работает точно так же. Или это просто конвертер "страховка"?

3 ответа

Решение

Я уверен, что это просто конвертер хака, и я думаю, что могу объяснить причину этого.

Но сначала, просто чтобы ответить на ваш вопрос, встроенный ++ Оператор в 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.

Я полагаю, это потому, что вы хотите увеличить значение в том же выражении, что и при сравнении. У меня нет слишком большого оправдания для этого, но это похоже на правильный ответ, так как trueCnt += 1 не позволяет сравнивать в одной строке. Это, конечно, не имеет ничего общего с тем, что он является foreach, попробуйте добавить ту же строку вне цикла, и я почти уверен, что она также преобразуется в Increment. В VB .Net просто нет другого синтаксиса для увеличения и сравнения в одной строке.

В дополнение к вышеупомянутым проблемам поведение C# по умолчанию следует за неудачным поведением переполнения целых чисел в Java. Хотя бывают случаи, когда полезно использовать семантику с целочисленным переполнением, C# обычно не делает различий между ситуациями, когда целочисленные значения должны переноситься по переполнению, по сравнению с ситуациями, когда целочисленные значения не должны переноситься, но программист не считает, что перехват переполнения имеет смысл. Это может запутать усилия по конвертации, потому что VB.NET делает поведение перехвата проще и быстрее, чем поведение обтекания, в то время как C# делает обратное. Следовательно, логический способ перевести код, который использует неконтролируемую математику по соображениям скорости, заключается в использовании обычной проверенной математики в VB.NET, в то время как логический способ перевести код, который требует поведения переноса, заключается в использовании целочисленных методов переноса в VB.NET., Threading.Interlocked.Increment а также Threading.Increment.Add методы используют целочисленное поведение, поэтому они не являются оптимальными с точки зрения скорости, но они удобны.

Другие вопросы по тегам