gpt4 book ai didi

c# - 使用 ParallelFor 循环时索引超出范围异常

转载 作者:可可西里 更新时间:2023-11-01 07:54:25 25 4
gpt4 key购买 nike

这是一个非常奇怪的情况,首先是代码......

编码

 private List<DispatchInvoiceCTNDataModel> WorksheetToDataTableForInvoiceCTN(ExcelWorksheet excelWorksheet, int month, int year)
{
int totalRows = excelWorksheet.Dimension.End.Row;
int totalCols = excelWorksheet.Dimension.End.Column;
DataTable dt = new DataTable(excelWorksheet.Name);
// for (int i = 1; i <= totalRows; i++)
Parallel.For(1, totalRows + 1, (i) =>
{
DataRow dr = null;
if (i > 1)
{
dr = dt.Rows.Add();
}
for (int j = 1; j <= totalCols; j++)
{
if (i == 1)
{
var colName = excelWorksheet.Cells[i, j].Value.ToString().Replace(" ", String.Empty);
lock (lockObject)
{
if (!dt.Columns.Contains(colName))
dt.Columns.Add(colName);
}
}
else
{
dr[j - 1] = excelWorksheet.Cells[i, j].Value != null ? excelWorksheet.Cells[i, j].Value.ToString() : null;
}
}
});
var excelDataModel = dt.ToList<DispatchInvoiceCTNDataModel>();
// now we have mapped everything expect for the IDs
excelDataModel = MapInvoiceCTNIDs(excelDataModel, month, year, excelWorksheet);
return excelDataModel;
}

问题
当我随机运行代码时,它会抛出 IndexOutOfRangeException在线上
  dr[j - 1] = excelWorksheet.Cells[i, j].Value != null ? excelWorksheet.Cells[i, j].Value.ToString() : null;

对于 i 的一些随机值和 j .当我跳过代码( F10 )时,由于它在 ParallelLoop 中运行,因此其他一些线程被踢出并抛出其他异常,该其他异常类似于(我无法重现它,它只出现过一次,但是我认为这也与这个线程问题有关) Column 31 not found in excelWorksheet .我不明白这些异常怎么会发生?

情况1 IndexOutOfRangeException甚至不应该发生,因为唯一的代码/共享变量 dt我已经锁定访问它,其余的都是本地或参数,所以不应该有任何与线程相关的问题。另外,如果我检查 i 的值或 j在调试窗口中,甚至评估整个表达式 dr[j - 1] = excelWorksheet.Cells[i, j].Value != null ? excelWorksheet.Cells[i, j].Value.ToString() : null;或者它的一部分在调试窗口中,然后它工作得很好,没有任何类型的错误或什么都没有。

案例2
对于第二个错误,(不幸的是现在没有重现,但仍然)它不应该发生,因为 excel 中有 33 列。

更多代码
如果有人可能需要如何调用此方法
using (var xlPackage = new ExcelPackage(viewModel.postedFile.InputStream))
{
ExcelWorksheets worksheets = xlPackage.Workbook.Worksheets;

// other stuff
var entities = this.WorksheetToDataTableForInvoiceCTN(worksheets[1], viewModel.Month, viewModel.Year);
// other stuff
}

其他
如果有人需要更多代码/详细信息,请告诉我。

更新
好的,回答一些评论。使用 for 时工作正常循环,我已经测试了很多次。此外, i 没有特定的值。或 j为其抛出异常。有时是 8, 6其他时候它可以是任何东西,比如 19,2或任何东西。此外,在 Parallel循环 +1没有像 msdn 那样造成任何损害文件说它是排他性的而不是包容性的。此外,如果这是问题,我只会在最后一个索引(i 的最后一个值)处出现异常,但事实并非如此。

更新 2
锁定代码的给定答案
  dr = dt.Rows.Add();

我已将其更改为
  lock(lockObject) {
dr = dt.Rows.Add();
}

它不工作。现在我收到 ArgumentOutOfRangeException ,如果我在调试窗口中运行它,它仍然可以正常工作。

更新 3
这是更新 2 之后的完整异常详细信息(我在更新 2 中提到的行中得到了这一点)
System.ArgumentOutOfRangeException was unhandled by user code
HResult=-2146233086
Message=Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index
Source=mscorlib
ParamName=index
StackTrace:
at System.ThrowHelper.ThrowArgumentOutOfRangeException()
at System.Collections.Generic.List`1.get_Item(Int32 index)
at System.Data.RecordManager.NewRecordBase()
at System.Data.DataTable.NewRecordFromArray(Object[] value)
at System.Data.DataRowCollection.Add(Object[] values)
at AdminEntity.BAL.Service.ExcelImportServices.<>c__DisplayClass2e.<WorksheetToDataTableForInvoiceCTN>b__2d(Int32 i) in C:\Projects\Manager\Admin\AdminEntity\AdminEntity.BAL\Service\ExcelImportServices.cs:line 578
at System.Threading.Tasks.Parallel.<>c__DisplayClassf`1.<ForWorker>b__c()
InnerException:

最佳答案

好的。因此,您现有的代码存在一些问题,其中大部分已被其他人触及:

  • 并行线程受操作系统调度程序的支配;因此,尽管线程按顺序排队,但它们可能(并且经常)无序完成执行。例如,给定 Parallel.For(0, 10, (i) => { Console.WriteLine(i); }); ,前四个线程(在四核系统上)将与 i 一起排队值 0-3。但是这些线程中的任何一个都可能在任何其他线程之前开始或完成执行。因此,您可能会首先看到 2 打印,因此线程 4 将排队。然后线程 1 可能完成,线程 5 将排队。然后线程 4 可能会完成,甚至在线程 0 或 3 完成之前。等等,等等。 TL;DR:你不能假设并行的有序输出。
  • 鉴于此,正如@ScottChamberlain 指出的那样,在并行循环中进行列生成是一个非常糟糕的主意 - 因为您无法保证进行列生成的线程会在另一个线程开始将行中的数据分配给这些列索引之前创建所有列.例如。您可以在表格实际包含第五列之前将数据分配给单元格 [0,4]。
  • 值得注意的是,无论如何,这真的应该被打破循环,纯粹从代码清洁的角度来看。目前,您有两个嵌套循环,每个循环在一次迭代中都有特殊行为;最好将该设置逻辑分离到它自己的循环中,并让主循环分配数据而不是其他任何东西。
  • 出于同样的原因,您不应该在并行循环中的表中创建新行 - 因为您无法保证这些行将按其源顺序添加到表中。也打破这一点,并根据它们的索引访问循环中的行。
  • 有些人在 Rows.Add() 之前提到了使用 DataRow.NewRow()。从技术上讲,NewRow() 是解决问题的正确方法,但实际推荐的访问模式与可能适用于逐个单元功能的情况略有不同,尤其是在打算并行时(请参阅 MSDN: DataTable.NewRow Method )。事实仍然是,使用 Rows.Add() 向 DataTable 添加一个新的空白行并随后填充它可以正常运行。
  • 您可以使用空合并运算符 ?? 清理字符串格式。 ,它评估前面的值是否为空,如果是,则分配后续值。例如,foo = bar ?? ""相当于 if (bar == null) { foo = ""; } else { foo = bar; } .

  • 所以马上,你的代码应该看起来更像这样:
    private void ReadIntoTable(ExcelWorksheet sheet)
    {
    DataTable dt = new DataTable(sheet.Name);
    int height = sheet.Dimension.Rows;
    int width = sheet.Dimension.Columns;

    for (int j = 1; j <= width; j++)
    {
    string colText = (sheet.Cells[1, j].Value ?? "").ToString();
    dt.Columns.Add(colText);
    }
    for (int i = 2; i <= height; i++)
    {
    dt.Rows.Add();
    }

    Parallel.For(1, height, (i) =>
    {
    var row = dt.Rows[i - 1];
    for (int j = 0; j < width; j++)
    {
    string str = (sheet.Cells[i + 1, j + 1].Value ?? "").ToString();
    row[j] = str;
    }
    });

    // convert to your special Excel data model
    // ...
    }

    好多了!

    ……但还是不行!

    是的,它仍然因 IndexOutOfRange 异常而失败。但是,由于我们采用了您的原始线路 dr[j - 1] = excelWorksheet.Cells[i, j].Value != null ? excelWorksheet.Cells[i, j].Value.ToString() : null;并将其分成几部分,我们可以确切地看到它失败的部分。它在 row[j] = str; 上失败了,我们实际将文本写入行的位置。

    哦哦。

    MSDN: DataRow Class

    Thread Safety

    This type is safe for multithreaded read operations. You must synchronize any write operations.



    *叹*。是的。谁知道为什么 DataRow 在分配值时使用静态的任何东西,但你已经知道了;写入 DataRow 不是线程安全的。果然,这样做...
    private static object s_lockObject = "";

    private void ReadIntoTable(ExcelWorksheet sheet)
    {
    // ...
    lock (s_lockObject)
    {
    row[j] = str;
    }
    // ...
    }

    ...神奇地使它起作用。当然,它完全破坏了并行性,但它有效。

    好吧,它几乎完全破坏了并行性。对具有 18 列和 46319 行的 Excel 文件进行的轶事实验表明,Parallel.For() 循环平均在大约 3.2 秒内创建其数据表,而将 Parallel.For() 替换为 for (int i = 1; i < height; i++)大约需要 3.5s。我的猜测是,由于锁仅用于写入数据,因此在一个线程上写入数据并在另一个线程上处理文本实现的好处非常小。

    当然,如果您可以创建自己的 DataTable 替换类,则可以看到更大的速度提升。例如:
    string[,] rows = new string[height, width];
    Parallel.For(1, height, (i) =>
    {
    for (int j = 0; j < width; j++)
    {
    rows[i - 1, j] = (sheet.Cells[i + 1, j + 1].Value ?? "").ToString();
    }
    });

    对于上面提到的同一个 Excel 表,这平均在大约 1.8 秒内执行 - 大约是我们几乎不平行的 DataTable 时间的一半。在这段代码中用标准 for() 替换 Parallel.For() 使其运行时间约为 2.5 秒。

    因此,您可以从并行性以及自定义数据结构中看到显着的性能提升 - 尽管后者的可行性将取决于您轻松将返回值转换为 Excel 数据模型事物的能力,无论它是什么。

    关于c# - 使用 ParallelFor 循环时索引超出范围异常,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29541767/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com