【问题标题】:AccessViolationException on Parallel.ForEachParallel.ForEach 上的 AccessViolationException
【发布时间】:2017-01-17 20:43:24
【问题描述】:

尝试运行 Parallel.ForEach 从外部库 Z4DLL_NET 中查找结果。 dll 的文档说该类型是多线程安全的。我们有一个大型数据集,我们每个月都会对它进行地址验证。

当运行任何大于 1 的批处理大小时,我在 Lookup 中的 _accumail.Lookup() 上收到 Access Violation Exception 错误。

我试图通过使用 MaxDegreeOfParallelism 来减少线程数量,但它并没有阻止这个问题。任何想法将不胜感激。

网络服务代码:

    public void ProcessByBatchId(int batchId, int batchSize)
    {
        // get addresses to process
        var allAddresses = GetAddresses(batchId);

        var count = 0;

        // get initial set of addresses to process
        var addresses = ParseAddresses(allAddresses, count, batchSize).ToList();

        while (addresses.Any())
        {
            count += addresses.Count();

            // connect to db
            using (var entities = new Entities())
            {
                // turn these options off since they aren't needed here
                entities.Configuration.AutoDetectChangesEnabled = false;
                entities.Configuration.ValidateOnSaveEnabled = false;
                entities.Configuration.ProxyCreationEnabled = false;
                entities.Configuration.LazyLoadingEnabled = false;

                // process each address in parallel
                Parallel.ForEach(
                    addresses, 
                    addr =>
                {
                    // create dictionary for processing
                    var fields = GetFields(addr);

                    using (var addressValidator = _addressValidatorFactory.Create())
                    {
                        // lookup
                        var results = addressValidator.Lookup(fields);

                        SetResults(addr, results);
                    }
                });

                // set entity as changed for update
                addresses.ForEach(addr => entities.Entry(addr).State = EntityState.Modified);

                // commit changes to db
                entities.SaveChanges();

                // get next set of addresses to process
                addresses = ParseAddresses(allAddresses, count, batchSize).ToList();
            }
        }


    }

查找代码:

    public ValidationResults Lookup(IDictionary<FieldEnum, string> values)
    {
        IDictionary<FieldEnum, string> results = null;

        try
        {
            // load each value into accumail obj
            foreach (var field in Enum.GetNames(typeof(FieldEnum)))
            {
                var z4Field = (Z4DLL.Field)Enum.Parse(typeof(Z4DLL.Field), field);
                var fieldEnum = (FieldEnum)Enum.Parse(typeof(FieldEnum), field);

                if (values.ContainsKey(fieldEnum))
                {
                    _accumail.PutField(z4Field, values[fieldEnum] ?? string.Empty);
                    continue;
                }

                _accumail.PutField(z4Field, string.Empty);
            }

            // perform lookup
            if (_accumail.Lookup())
            {
                results = new Dictionary<FieldEnum, string>();

                // get each field from accumail obj
                foreach (var field in Enum.GetNames(typeof(FieldEnum)))
                {
                    results.Add((FieldEnum)Enum.Parse(typeof(FieldEnum), field),
                                _accumail.GetField((Z4DLL.Field)Enum.Parse(typeof(Z4DLL.Field), field)));
                }
            }

            var errorNum = _accumail.GetErrorNum();

            return new ValidationResults(results, errorNum, _accumail.GetErrorMsg(errorNum));
        }
        catch
        {
            var errorNum = _accumail.GetErrorNum();

            return new ValidationResults(results, errorNum, _accumail.GetErrorMsg(errorNum));
        }
    }

错误说明:

System.AccessViolationException 未处理 HResult=-2147467261
Message=试图读取或写入受保护的内存。这通常是一个 指示其他内存已损坏。来源=Z4DLL32_NET
堆栈跟踪: 在 Smartsoft.Toolkit.Z4DLL.Lookup() 在 Accumail.AccumailAddressValidator.Lookup(字典 2 值)在 AccumailAddressValidator.cs:line 50 在 AddressValidationService.ProcessByBatchId>b__3_0(address_validation_detail addr) 在 AddressValidationService.svc.cs:line 145 在 System.Threading.Tasks.Parallel.c__DisplayClass31_0 2.b__0(Int32 一世) 在 System.Threading.Tasks.Parallel.c__DisplayClass17_0 1.b__1() 在 System.Threading.Tasks.Task.InnerInvoke() 在 System.Threading.Tasks.Task.InnerInvokeWithArg(任务子任务) 在 System.Threading.Tasks.Task.c__DisplayClass176_0.b__0(对象 ) 在 System.Threading.Tasks.Task.InnerInvoke() 在 System.Threading.Tasks.Task.Execute() 在 System.Threading.Tasks.Task.ExecutionContextCallback(对象 obj) 在 System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext、ContextCallback 回调、对象状态、布尔值 保留SyncCtx) 在 System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback 回调, 对象状态, Boolean 保留SyncCtx) 在 System.Threading.Tasks.Task.ExecuteWithThreadLocal(任务和 currentTaskSlot) 在 System.Threading.Tasks.Task.ExecuteEntry(布尔 bPreventDoubleExecution) 在 System.Threading.Tasks.Task.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem() 在 System.Threading.ThreadPoolWorkQueue.Dispatch() 在 System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()

编辑:

类型是

private readonly Z4DLL _accumail;

这是 Smartsoft.Toolkit 的一部分。当 AddressValidator 被初始化时,构造函数有

_accumail = new Z4DLL(databasePath);

【问题讨论】:

  • Lookup 方法有一些突变代码_accumail.PutField(...),这很可能导致问题。 _accumail 的类型是什么,它是线程安全的吗?
  • @sly 框架文档显示 Accumail Z4DLL 是线程安全的。
  • 基于异常StackTrace: at Smartsoft.Toolkit.Z4DLL.Lookup() at 我敢打赌文档不准确或至少不打算以这种方式使用。除了使用工厂方法_addressValidatorFactory.Create()之外,还有其他方法可以实例化新的addressValidator吗?
  • @sly 是的,我就是这么想的。我也尝试了 var avf = new addressValidator 并且出现了同样的问题。我会在这里与公司核实。

标签: c# .net multithreading


【解决方案1】:

只使用一个 dll 实例并多次调用它可能会更好。

 using (var addressValidator = _addressValidatorFactory.Create())
 {               {

Parallel.ForEach(
                addresses, 
                addr =>
            {
                // create dictionary for processing
                var fields = GetFields(addr);

                                        // lookup
                    var results = addressValidator.Lookup(fields);

                    SetResults(addr, results);
                }
            });
 }

注意 以上仅作为示例 - 我没有测试您的逻辑以查看在不进行其他更改的情况下是否可以正常工作。重点是展示如何将它拉到 for 循环之外。

【讨论】:

  • 我得到与上面相同的错误。所以我只使用工厂一次。
猜你喜欢
  • 2019-11-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-11-15
  • 1970-01-01
  • 2021-01-04
相关资源
最近更新 更多