【问题标题】:Task Parallel Library throws NullReferenceException with IEnumerable任务并行库抛出带有 IEnumerable 的 NullReferenceException
【发布时间】:2016-02-04 10:41:24
【问题描述】:

我有一个 IEnumerable 实体,它拥有大约 10 万条记录。我想执行 Parallel.ForEach 来插入这些数据。

说这里是我所拥有的类:Employee.cs

SqlConneciton conn = base.GetConnection();
conn.open();
IEnumerable<Employee> employeeList = GetListofEmployeesFromDB();
Parallel.ForEach(employeeList
, employee =>
{
    employee.add(conn, sqlTransaction);
});

Empployee.cs 
{
      public void add(SqlConnection conn, SqlTransaction sqlTransaction)
      {
             using (SqlCommand insertCmd = new SqlCommand("EmployeeInsert", conn))
             {
                 insertCmd.CommandType = CommandType.StoredProcedure;
                 insertCmd.Transaction = transaction;
                 insertCmd.Parameters["@Name"].Value = this.Name;
                 insertCmd.ExecuteNonQuery();
                 this.id = (int)insertCmd.Parameters["@Id"].Value;
             }
      }
}

随着数据的插入,我看到有一个NPE在:

this.id = (int)insertCmd.Parameters["@Id"].Value;

不确定我是否遗漏了什么。这是我看到的例外。

System.AggregateException was unhandled
  Message=AggregateException_ctor_DefaultMessage
  Source=System.Threading
  StackTrace:
       at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken)
       at System.Threading.Tasks.Task.Wait()
       at System.Threading.Tasks.Parallel.PartitionerForEachWorker[TSource,TLocal](Partitioner`1 source, ParallelOptions parallelOptions, Action`1 simpleBody, Action`2 bodyWithState, Action`3 bodyWithStateAndIndex, Func`4 bodyWithStateAndLocal, Func`5 bodyWithEverything, Func`1 localInit, Action`1 localFinally)
       at System.Threading.Tasks.Parallel.ForEach[TSource](Partitioner`1 source, ParallelOptions parallelOptions, Action`1 body)
        :
        :
        :
       at System.Threading.ExecutionContext.runTryCode(Object userData)
       at System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(TryCode code, CleanupCode backoutCode, Object userData)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       at System.Threading.ThreadHelper.ThreadStart(Object obj)
  InnerException: System.NullReferenceException
       Message=Object reference not set to an instance of an object.
       Source=Jobvite.Library
       StackTrace:            
            :
            :
            :
            at System.Threading.Tasks.Parallel.<>c__DisplayClass32`2.<PartitionerForEachWorker>b__30()
            at System.Threading.Tasks.Task.InnerInvoke()
            at System.Threading.Tasks.Task.InnerInvokeWithArg(Task childTask)
            at System.Threading.Tasks.Task.<>c__DisplayClass3.<ExecuteSelfReplicating>b__2(Object )
       InnerException: 

【问题讨论】:

  • 出于好奇,如果您在Employee.Add 中使用新连接,您会收到同样的错误吗?
  • stacktrace中只有:的行是什么意思?
  • @balah:我可以尝试,但建议不要将 Parallel.ForEach 用于 DB。
  • @henk:NPE 是空指针异常,我的错……是的,我没有意识到每个人都算数十万……更新它……
  • @alex:我删除了一些我不想粘贴到这里的代码。

标签: c# .net task-parallel-library parallel.foreach


【解决方案1】:

System.AggregateException 引发了从应用程序引发多个异常的可能性。

原因
您正在以并行模式访问 Connection 对象。多个任务试图同时访问它,并在它们无法获取它时引发异常。同一时刻只有一个线程可以访问数据库连接。
创建多个线程将数据插入数据库无论如何都不会加快速度。 (即使您设法找到任何并行方法)因为每次写入数据时数据库都会锁定,并且所有数据都将按顺序插入。

使用正常的插入过程,它会快得多。

【讨论】:

    【解决方案2】:

    (一旦我弄清楚“1 lac”是什么)看起来你想做一个批量插入。您可以使用 SqlBulkCopy 来做到这一点 - 它旨在有效地加载 SQL 表。

    但是,我看到您也想要返回 id,因此上述内容不会让您一路走好。我看到您正在使用存储过程,所以这是一种方法(假设您有 SQL 2008 及更高版本):

    1. 创建一个表值数据类型以包含您要插入的数据。

      CREATE TYPE [dbo].[EmployeeDataType] As Table
      (
         ID INT,
         -- employee details
      )
      
    2. 更改您的存储过程以使用此表值参数作为输入,当它执行插入时,它会执行并输出。例如

      CREATE PROCEDURE [dbo].[EmployeeInsert]
      (
         @EmployeeInsertParameter As [dbo].[EmployeeDataType] READONLY
      )
      AS
      ...
      
      INSERT INTO Employee
      SELECT * FROM @EmployeeInsertParameter e
      OUTPUT INSERTED.*
      

    (显然你会命名列而不使用 *)

    1. 将您的代码更改为使用Parallel.ForEach,而是这样做:

      DataTable employeeDataTable = new DataTable("EmployeeDataType");
      // fill in the rows using
      ...
      insertCmd.Parameters["@EmployeeInsertParameter"].Value = employeeDataTable;
      ...
      
    2. 将存储过程执行的结果读入List&lt;Employee&gt;

    结论:基本上不要使用Parallel.For进行数据库连接。这种方式将让您正确使用一个连接(不会导致“NPE”),并且大部分处理将在内存中完成,只要您有 RAM,它就会快几个数量级。


    这是另一种可能的方式,但涉及更多:https://stackoverflow.com/a/21689413/3419825

    【讨论】:

      猜你喜欢
      • 2020-10-22
      • 1970-01-01
      • 2014-06-17
      • 1970-01-01
      • 1970-01-01
      • 2021-10-02
      • 1970-01-01
      • 2013-05-30
      • 1970-01-01
      相关资源
      最近更新 更多