C# AccessViolationException与Parallel.For()一起使用

C# AccessViolationException与Parallel.For()一起使用,c#,crash,task-parallel-library,parallel-extensions,C#,Crash,Task Parallel Library,Parallel Extensions,下面的代码片段展示了我能够从生产代码中分离出来的一个bug。程序将因系统崩溃。AccessViolationException 我的系统配置是Windows 8.1 x64上的Visual Studio 2013。要重现该错误,请在Visual Studio中执行以下步骤: 创建一个空的控制台应用程序 用以下代码替换Program.cs的全部内容 在发布模式下编译,任何CPU 在不调试的情况下启动(CTRL+F5) 程序将立即崩溃,在控制台窗口中显示错误堆栈跟踪 我无法进一步减少代码,即修改代码

下面的代码片段展示了我能够从生产代码中分离出来的一个bug。程序将因
系统崩溃。AccessViolationException

我的系统配置是Windows 8.1 x64上的Visual Studio 2013。要重现该错误,请在Visual Studio中执行以下步骤:

  • 创建一个空的控制台应用程序
  • 用以下代码替换Program.cs的全部内容
  • 发布模式下编译,任何CPU
  • 在不调试的情况下启动(CTRL+F5)
  • 程序将立即崩溃,在控制台窗口中显示错误堆栈跟踪

    我无法进一步减少代码,即修改代码的任何部分都会使bug消失

    有没有办法找出这个错误的根本原因?什么是好的解决办法

    using System;
    using System.Threading.Tasks;
    
    namespace ParallelBugTest
    {
        class Program
        {
            private class Value
            {
                public double X;
            }
    
            private class Aggregator
            {
                private Value myval = new Value();
    
                public void Add()
                {
                    // use Min() instead of Max() -> bug disappears
                    myval.X = Math.Max(0, myval.X);
                }
            }
    
            public static void Main(string[] args)
            {
                Parallel.For(0, 10000, Process);
            }
    
            private static void Process(int k)
            {
                Value[] V = new Value[10000];
                Aggregator aggregator = new Aggregator();
    
                for (int i = 0; i < V.Length; i++)
                {
                    V[i] = new Value();
                    aggregator.Add();
                }
            }
        }
    }
    
    使用系统;
    使用System.Threading.Tasks;
    命名空间并行错误测试
    {
    班级计划
    {
    私有阶级价值
    {
    公共双X;
    }
    私有类聚合器
    {
    私有值myval=新值();
    公共无效添加()
    {
    //使用Min()代替Max()->错误消失
    myval.X=Math.Max(0,myval.X);
    }
    }
    公共静态void Main(字符串[]args)
    {
    平行。对于(0,10000,过程);
    }
    私有静态无效进程(int k)
    {
    值[]V=新值[10000];
    聚合器聚合器=新聚合器();
    对于(int i=0;i
    Math.Max不是线程安全的。我想Min在工作,因为它计算起来更快。 锁定Max调用会起作用:

        private static Object lockObj = new Object();
    
        private class Value
        {
            public double X;
        }
    
        private class Aggregator
        {
            private Value myval = new Value();
    
            public void Add()
            {
                // use Min() instead of Max() -> bug disappears
                lock (lockObj)
                { 
                    myval.X = Math.Max(0, myval.X);
                }
            }
        }
    
        public static void Main(string[] args)
        {
            Parallel.For(0, 10000, Process);
        }
    
        private static void Process(int k)
        {
            Value[] V = new Value[10000];
            Aggregator aggregator = new Aggregator();
    
            for (int i = 0; i < V.Length; i++)
            {
                V[i] = new Value();
                aggregator.Add();
            }
        }
    

    代码中的所有内容都是线程安全的,因为线程之间没有共享状态(每个迭代都有自己的
    聚合器
    数组,并且不使用任何
    静态
    字段)

    即使您的代码不是线程安全的,它也不会做任何不安全的事情(即直接使用内存),这应该是获取
    AccessViolationException
    的唯一方法


    因此,我认为这是CLR中的一个bug,您应该(登录后单击“提交反馈”链接)。

    我不知道任何
    数学
    成员都不会是线程安全的?
    Math
    类的文档明确指出:“此类型的任何公共静态成员都是线程安全的。任何实例成员都不能保证是线程安全的。”对于非线程安全,我的意思是这是一个与多线程相结合的Bug。正如斯维克所说,你可能想报告。如果你需要一个快速的解决方案,你可以使用锁或者创建自己的Max方法,如我的帖子所示。有趣的是:
    Parallel.For(0,2000,Process)只是偶尔会崩溃。我可以证明,这是一个与多线程相结合的Bug。所以我不把有缺陷的方法称为线程安全的。@MatthiasMüller什么是线程不安全的?仅因为在这种特定情况下,仅当将
    Math.Max()
    与多线程一起使用时才会出现错误,并不意味着
    Math.Max()
    不是线程安全的。
            public void Add()
            {
                // use Min() instead of Max() -> bug disappears
                myval.X = myval.X > 0 ? myval.X : 0;
            }