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模式下的示例.
>我在堆栈中使用ThreadPool.SetMaxThreads(60,60),因为我在某个时候查询Access数据库.这可能会造成问题吗?
>可以调用Increment混合Parallel.ForEach循环,强制它在所有任务完成之前退出?

更新2(方法):

>我的测试执行的代码尽可能接近这里显示的内容,除了对象类型和查询字符串.
>查询总是给出相同数量的结果,我总是验证objects.Count在断点上继续Parallel.ForEach.
>在执行之间更改的唯一代码是Interlocked.Increment替换为SyncLock locker和countObjects = 1.

更新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参数传递的属性到类似于上述的代码.

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


文章浏览阅读6.2k次,点赞2次,收藏3次。C#数学运算表达式解释器测试文件内容:a=2+3*2;b=2*(2+3);浏览按钮事件处理程序: private void button_browse_Click(object sender, EventArgs e) { OpenFileDialog fbd = new OpenFileDialog(); fbd.T_c# 表达式分析器
文章浏览阅读5.2k次,点赞6次,收藏7次。程序要做到用户配置的灵活性,就需要添加配置管理功能,这里使用.NET的应用程序配置文件app.config来保存配置信息,.NET Framework提供了对配置文件读写的良好支持。要实现配置文件的读取功能,需要引用System.Configuration命名空间。提供源码下载,有源有真相。_引用封送类的字段,访问上面的成员可能导致运行时异常
文章浏览阅读9k次。错误信息检测到 ContextSwitchDeadlock Message: CLR 无法从 COM 上下文 0x622b440 转换为 COM 上下文 0x622b5b0,这种状态已持续 60 秒。拥有目标上下文/单元的线程很有可能执行的是非泵式等待或者在不发送 Windows 消息的情况下处理一个运行时间非常长的操作。这种情况通常会影响到性能,甚至可能导致应用程序不响应或者使用的内存随时间不断_contextswitchdeadlock
文章浏览阅读2w次,点赞10次,收藏9次。我发生错误时的环境:Windows 7,Framework 4、0,Microsoft Office 2007,VS2010,c# WinForm;部分代码: string strConn = "Provider=Microsoft.Ace.OleDb.12.0;Persist Security Info=False;" + "data source=" + _c# oledb 操作必须使用一个可更新的查询
文章浏览阅读9.8k次。C# 二进制字节流查找函数IndexOf /// /// 报告指定的 System.Byte[] 在此实例中的第一个匹配项的索引。 /// /// 被执行查找的 System.Byte[]。 /// 要查找的 System.Byte[]。 /// 如果找到该字节数组,则为 searchBytes 的索_c#byte[]查找
文章浏览阅读2.5w次,点赞3次,收藏9次。c#DataGridView数据绑定示例 格式化单元格的内容在使用DataGridView显示数据库中的数据时,我们需要对某列的数据显示格式进行格式化。这里使用实时构建的数据,如下图:在显示时对第三列的数据进行格式化,如下图:测试数据构建及数据绑定: private void Form1_Load(object sender, EventArgs e) { _c#datatable列格式化
文章浏览阅读2.8w次,点赞3次,收藏4次。完整错误信息错误 1 命名空间“System”中不存在类型或命名空间名称“Linq”。是否缺少程序集引用? F:CsProjectsCSharp实现SPY++CSharp实现SPY++Form1.cs 6 14 CSharp实现SPY++错误原因开始的时候创建项目选择的Framework版本是4.0,但后来为了项目的平台适应性,将Framework的版本改为了2.0,重新编译_命名空间system中不存在类型或命名空间名称
文章浏览阅读1.9w次。一、通过配置文件实现以管理员身份运行程序Vista 和 Windows 7 操作系统为了加强安全,增加了 UAC(用户账户控制) 的机制,如果 UAC 被打开,用户即使是以管理员权限登录,其应用程序默认情况下也无法对系统目录,系统注册表等可能影响系统运行的设置进行写操作。这个机制大大增强了系统的安全性,但对应用程序开发者来说,我们不能强迫用户去关闭UAC,但有时我们开发的应用程序又需要_c# 默认程序以管理身份运行。
文章浏览阅读5.2k次。在使用C#操作IIS创建应用程序池出现异常:无效索引(Exception from HRESULT:0x80070585)相关代码:public static string CreateAppPool(string appPoolName, string frameworkVersion, string managedPipelineMode) {_create website 无效索引。 (0x80070585)
文章浏览阅读9.5k次,点赞3次,收藏4次。C#二进制字节数组操作函数 截取字节数组SubByte /// /// 截取字节数组 /// /// 要截取的字节数组 /// 开始截取位置的索引 /// 要截取的字节长度 /// 截取后的字节数组 public byte[] SubByte(byte[] srcByt_c#字节数组截取
文章浏览阅读2.4w次,点赞5次,收藏16次。C#是微软公司发布的一种面向对象的、运行于.NET Framework之上的高级程序设计语言。并定于在微软职业开发者论坛(PDC)上登台亮相。C#是微软公司研究员Anders Hejlsberg的最新成果。C#看起来与Java有着惊人的相似;它包括了诸如单一继承、接口、与Java几乎同样的语法和编译成中间代码再运行的过程。但是C#与Java有着明显的不同,它借鉴了Delphi的一个特点,与COM(_c#读文件
文章浏览阅读4.8w次,点赞12次,收藏44次。C#创建Excel文件,这里实际上是从资源中提取一个事先创建好的Excel文件,文件提取成功后,使用OleDb方法连接Excel,向Excel文件中写入数据。创建解决方案菜单》新建》项目》Windows窗体应用程序:添加相关组件:添加两个DataGridView,一个TextBox,两个按钮 ,如下图:添加Excel资源:先在文件夹中新建一个Excel文件,在Sheet1表的第一行设置列名:双击“_c#保存到excel
文章浏览阅读2.8k次。windows 7和vista提高的系统的安全性,同时需要明确指定“以管理员身份运行”才可赋予被运行软件比较高级的权限,比如访问注册表等。否则,当以普通身份运行的程序需要访问较高级的系统资源时,将会抛出异常。如何让程序在启动时,自动要求“管理员”权限了,我们只需要修改app.manifest文件中的配置项即可。app.manifest文件默认是不存在的,我们可以通过以下操作来自_vb.net 程式以管理员运行
文章浏览阅读6.1k次,点赞4次,收藏7次。窗口风格(Window style)WS_BORDER 有边框窗口 WS_CAPTION 必须和WS_BORDER风格配合,但不能与WS_DLGFRAME风格一起使用。指示窗口包含标题要部分。 WS_CHILD 说明窗口为子窗口,不能应用于弹出式窗口风格(WS_POPUP)。 WS_CHILDWINDOW 同WS_CHILD。 WS_CLIPCHILDREN 绘制父窗口时_net(c#):ws_caption | ws_border
文章浏览阅读1.8w次,点赞3次,收藏9次。C#修改文件或文件夹的权限,为指定用户、用户组添加完全控制权限 //给Excel文件添加"Everyone,Users"用户组的完全控制权限 FileInfo fi = new FileInfo(excelPath); System.Security.AccessControl.FileSecurity fileSecurity_c# 判断 文件夹 是否 users 用户组 写入 权限
文章浏览阅读9.3k次。C# 模拟PrintScreen 和 Alt+PrintScreen截取屏幕图片keybd_event API函数功能:该函数合成一次击键事件。系统可使用这种合成的击键事件来产生WM_KEYUP或WM_KEYDOWN消息,键盘驱动程序的中断处理程序调用keybd_event函数。在Windows NT中该函数己被使用SendInput来替代它。函数原型;VOID keybd_event..._如何编程调用 printscreen
文章浏览阅读1w次。这本来是在VS2005下创建的一下项目,后来改用VS2010的开发环境,.NET Framework的版本还是使用2.0,但每次生成之后都会在解决方案的同级目录下产生一个名称乱码的文件夹,解决了那个问题之后,由于这个Windows窗体应用程序添加一个安装项目,项目生成时出现以下错误:错误 1 验证时出错。HRESULT = '8000000A' F:CsProjects屏幕截图2005屏幕截_error1an error occurred while validating. hresult = '8000000a
文章浏览阅读7.3k次。上一篇:C#软件开发实例.私人订制自己的屏幕截图工具(六)添加配置管理功能由于截图时可能需要精确截取某一部分,所以需要放大镜的功能,这样截取的时候才更容易定位截图的位置。添加PictureBox,name属性设置为“pictureBox_zoom”;在“Form1_Load”事件处理函数中添加以下代码://设置放大镜的大小 this.pictureBox_zoom.Widt_c#实现放大镜效果
文章浏览阅读4.5k次。C# 绘制箭头的方法,仿微信截图的箭头效果见下图,实际上还是有区别的,箭头的起点处微信的是圆端,而我实现的是尖端。说说我的实现吧,实现方法其实是划线,线的两端都要设置端点样式。看代码:Point _StarPoint = new Point(0, 0);Point _EndPoint = new Point(300, 300);System.Drawing.Drawing2..._adjustablearrowcap
文章浏览阅读1.3w次,点赞3次,收藏4次。在实现“C#软件开发实例.私人订制自己的屏幕截图工具(六)添加配置管理功能”功能时,遇到警告:由于“Screenshot.Form1.ZoomBoxHeight”是引用封送类的字段,访问上面的成员可能导致运行时异常解决方案:对字段对待封装:在需要封装的字段上单击鼠标右键,重构》封装字段:输入属性名:使用默认设置,单击应用_由于引入封装类的字段