c# – 为什么Interlocked.Increment在Parallel.ForEach循环中给
我有一个迁移作业,我需要在完成后验证目标数据.要通知管理员验证的成功/失败,我使用计数器来比较
Database1中表Foo的行数与Database2中表Foo的行数.
Database2中的每一行都针对Database1中的相应行进行验证.为了加快进程,我使用Parallel.ForEach循环. 我最初的问题是,这个计数总是与我预想的不同.我后来发现,=和 – =操作不是线程安全的(不是原子的).为了纠正这个问题,我更新了使用Interlocked.Increment的代码来计数变量.这个代码打印一个更接近实际计数的计数,但仍然在每次执行时似乎都有所不同,它没有给出我预期的结果: Private countObjects As Integer Private Sub MyMainFunction() Dim objects As List(Of MyObject) 'Query with Dapper,unrelevant to the problem. Using connection As New System.Data.SqlClient.SqlConnection("aConnectionString") objects = connection.Query("SELECT * FROM Foo") 'Returns around 81000 rows. End Using Parallel.ForEach(objects,Sub(u) MyParallelFunction(u)) Console.WriteLine(String.Format("Count : {0}",countObjects)) 'Prints "Count : 80035" or another incorrect count,which seems to differ on each execution of MyMainFunction. End Sub Private Sub MyParallelFunction(obj As MyObject) Interlocked.Increment(countObjects) 'Breakpoint Hit Count is at around 81300 or another incorrect number when done. 'Continues executing unrelated code using obj... End Sub 在使用其他增加线程安全性的方法进行了一些实验之后,我发现将虚拟参考对象上的SyncLock中的增量包装起来会给出预期的结果: Private countObjects As Integer Private locker As SomeType Private Sub MyMainFunction() locker = New SomeType() Dim objects As List(Of MyObject) 'Query with Dapper,countObjects)) 'Prints "Count : 81000". End Sub Private Sub MyParallelFunction(obj As MyObject) SyncLock locker countObjects += 1 'Breakpoint Hit Count is 81000 when done. End SyncLock 'Continues executing unrelated code using obj... End Sub 为什么第一个代码段不按预期工作?最令人困惑的是断点命中计数提供意想不到的结果. 我对Interlocked.Increment或原子操作的理解有缺陷吗?我不想在一个虚拟对象上使用SyncLock,我希望有一个干净的方法. 更新: >我在任何CPU上运行Debug模式下的示例. 更新2(方法): >我的测试执行的代码尽可能接近这里显示的内容,除了对象类型和查询字符串. 更新3 我通过在新的控制台应用程序中复制我的代码并替换外部类和代码来创建了一个SSCCE. 这是控制台应用程序的主要方法: Sub Main() Dim oClass1 As New Class1 oClass1.MyMainFunction() End Sub 这是Class1的定义: Imports System.Threading Public Class Class1 Public Class Dummy Public Sub New() End Sub End Class Public Class MyObject Public Property Id As Integer Public Sub New(p_Id As Integer) Id = p_Id End Sub End Class Public Property countObjects As Integer Private locker As Dummy Public Sub MyMainFunction() locker = New Dummy() Dim objects As New List(Of MyObject) For i As Integer = 1 To 81000 objects.Add(New MyObject(i)) Next Parallel.ForEach(objects,Sub(u As MyObject) MyParallelFunction(u) End Sub) Console.WriteLine(String.Format("Count : {0}",countObjects)) 'Interlock prints an incorrect count,different in each execution. SyncLock prints the correct count. Console.ReadLine() End Sub 'Interlocked Private Sub MyParallelFunction(ByVal obj As MyObject) Interlocked.Increment(countObjects) End Sub 'SyncLock 'Private Sub MyParallelFunction(ByVal obj As MyObject) ' SyncLock locker ' countObjects += 1 ' End SyncLock 'End Sub End Class 将MyParallelFunction从Interlocked.Increment切换到SyncLock时,我仍然注意到相同的行为. 解决方法
一个财产的增值将永远被打破.有效地,VB编译器将其重写为:
Value = <value from Property> Interlocked.Increment(Value) <Property> = Value 因此打破了Increment提供的任何线程保证.将其更改为字段. VB将重写任何作为ByRef参数传递的属性到类似于上述的代码. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |