Raz*_*t4x 14 c# multithreading .net-4.0 task-parallel-library
这是一个非常奇怪的情况,首先是代码......
代码
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;
}
Run Code Online (Sandbox Code Playgroud)
问题
当我在随机场合运行代码时,它会抛出IndexOutOfRangeException
线
dr[j - 1] = excelWorksheet.Cells[i, j].Value != null ? excelWorksheet.Cells[i, j].Value.ToString() : null;
Run Code Online (Sandbox Code Playgroud)
对于一些随机值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
}
Run Code Online (Sandbox Code Playgroud)
其他
如果有人需要更多代码/细节,请告诉我.
更新
好的,回答一些评论.它是在使用工作正常for
循环,我测试过很多次.此外,没有特定的值i
或j
抛出异常的值.有时它8, 6
在其他时间可能是任何东西,说19,2
或任何东西.此外,在Parallel
循环+1
中没有造成任何损害,因为msdn文档说它是独占的,不包括在内.此外,如果那是问题,我只会在最后一个索引(i的最后一个值)获得异常,但事实并非如此.
更新2
锁定代码的给定答案
dr = dt.Rows.Add();
Run Code Online (Sandbox Code Playgroud)
我把它改成了
lock(lockObject) {
dr = dt.Rows.Add();
}
Run Code Online (Sandbox Code Playgroud)
它不起作用.现在我得到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:
Run Code Online (Sandbox Code Playgroud)
Dio*_*nin 14
好的.因此,现有代码存在一些问题,其中大部分都被其他代码所触及:
Parallel.For(0, 10, (i) => { Console.WriteLine(i); });
,前四个线程(在四核系统上)将排队,i
值为0-3.但是这些线程中的任何一个都可以在任何其他线程之前开始或完成.因此,您可能会先看到2个打印,然后第4个线程将排队.然后线程1可能完成,线程5将排队.然后线程4可能完成,甚至在线程0或3之前.等等TL; DR:您不能并行地假设有序输出.??
计算前面的值是否为null,如果是,则指定后续值.例如,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
// ...
}
Run Code Online (Sandbox Code Playgroud)
好多了!
......但它仍然不起作用!
是的,它仍然因IndexOutOfRange异常而失败.但是,由于我们采用原始线dr[j - 1] = excelWorksheet.Cells[i, j].Value != null ? excelWorksheet.Cells[i, j].Value.ToString() : null;
并将其拆分成几块,我们可以确切地看到它失败的部分.它失败了row[j] = str;
,我们实际上将文本写入行.
嗯,哦.
线程安全
此类型对于多线程读取操作是安全的.您必须同步任何写操作.
*叹*.是啊.谁知道为什么DataRow在分配值时会使用静态的东西,但是你有它; 写入DataRow不是线程安全的.当然,这样做......
private static object s_lockObject = "";
private void ReadIntoTable(ExcelWorksheet sheet)
{
// ...
lock (s_lockObject)
{
row[j] = str;
}
// ...
}
Run Code Online (Sandbox Code Playgroud)
......神奇地让它发挥作用.当然,它完全破坏了并行性,但它确实有效.
好吧,它几乎彻底摧毁了并行性.对包含18列和46319行的Excel文件进行的轶事实验表明,Parallel.For()循环平均在大约3.2s内创建其DataTable,而用for (int i = 1; i < height; i++)
大约3.5s 替换Parallel.For().我的猜测是,由于锁仅用于写入数据,因此通过在一个线程上写入数据并在另一个线程上处理文本来实现非常小的好处.
当然,如果你可以创建自己的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();
}
});
Run Code Online (Sandbox Code Playgroud)
对于上面提到的同一个Excel表,这平均执行大约1.8秒 - 大约是我们几乎没有并行DataTable的一半时间.使用此片段中的()标准替换Parallel.For()使其在大约2.5秒内运行.
所以你可以从并行性看到显着的性能提升,但也可以从自定义数据结构中看到 - 虽然后者的可行性取决于你能否轻松地将返回值转换为Excel数据模型的东西,无论它是什么.
线 dr = dt.Rows.Add();
不是线程安全的,您正在破坏保存表行的 DataTable 中数组的内部状态。
乍一看把它改成
if (i > 1)
{
lock (lockObject)
{
dr = dt.Rows.Add();
}
}
Run Code Online (Sandbox Code Playgroud)
应该修复它,但这并不意味着excelWorksheet.Cells
从多个线程访问其他线程安全问题不存在。(如果excelWorksheet
是这个类并且您正在运行 STA 主线程(WinForms 或 WPF)COM 应该为您编组跨线程调用)
编辑:新理论,问题来自于您在并行循环内设置架构并尝试同时写入它的事实。将所有i == 1
逻辑拉到循环之前,然后从i == 2
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);
//Build the schema before we loop in parallel.
for (int j = 1; j <= totalCols; j++)
{
var colName = excelWorksheet.Cells[1, j].Value.ToString().Replace(" ", String.Empty);
if (!dt.Columns.Contains(colName))
dt.Columns.Add(colName);
}
Parallel.For(2, totalRows + 1, (i) =>
{
DataRow dr = null;
lock(lockObject) {
dr = dt.Rows.Add();
}
for (int j = 1; j <= totalCols; j++)
{
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;
}
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
3385 次 |
最近记录: |