执行以下代码时。
using System;
using System.Collections.Generic;
using System.Data;
using System.Threading.Tasks;
namespace AsyncDataRow
{
internal class Program
{
private static void Main(string[] args)
{
var program = new Program();
try
{
Console.WriteLine("Execute");
for (int i = 0; i < 100; i++)
{
program.Execute();
}
}
catch (AggregateException aggregateException)
{
foreach (var exception in aggregateException.InnerExceptions)
{
Console.WriteLine("AggregateException.InnerExceptions: " + exception.Message);
}
}
catch (Exception ex)
{
Console.WriteLine("Exception.Message: " + ex.Message);
}
Console.ReadKey();
}
private void Execute()
{
DataTable dataTable = new DataTable();
dataTable.Columns.Add("ID", typeof(int));
dataTable.Columns.Add("Name", typeof(string));
for (int i = 0; i < 1000; i++)
{
dataTable.Rows.Add(i, i.ToString());
}
var taskList = new List<Task>();
foreach (DataRow dataRow in dataTable.Rows)
{
taskList.Add(ChangeStringValue(dataRow));
}
Task.WaitAll(taskList.ToArray());
}
private async Task ChangeStringValue(DataRow dataRow)
{
var id = (int)dataRow["ID"];
var newValue = await Task.Run(() => CreateNewValue(id));
dataRow["Name"] = newValue;
}
private string CreateNewValue(int id)
{
Console.WriteLine(string.Format("{0} - CreateNewValue", id));
return id.ToString() + "New StringValue";
}
}
}
在这行代码中间歇性地抛出System.ArgumentOutOfRangeException
dataRow["Name"] = newValue
异常消息:
Exception thrown: 'System.ArgumentOutOfRangeException' in mscorlib.dll
Additional information: Index was out of range. Must be non-negative and less than the size of the collection.
我正在尝试更具体地确定问题,但我最接近的是简化代码以重现错误。
我认为它与传递给异步方法的DataRow引用类型有关,但我想知道为什么这段代码不是线程安全的。
答案 0 :(得分:1)
.Net框架中的线程安全类的数量非常少,每个类都会明确地调出其安全性(大多数都在System.Collecions.Concurrent
命名空间中找到)。对于所有其他类型,您需要提供自己的线程安全机制。 DB相关的类肯定属于非线程安全的类别。通常,类显式允许多个线程安全读取。
在您的情况下,DataRow
可以从多个线程读取,您只需要同步写入:
线程安全
此类型对于多线程读取操作是安全的。您必须同步任何写操作。
构建批次项目的标准方法 - 在范围中拆分项目并独立生成每个项目,然后在完成后合并。
基于原始代码的部分示例,MyRowResult
是用于保存需要更新的行的所有数据的类。
var taskList = new List<Task<MyDataRow>();
foreach (DataRow dataRow in dataTable.Rows)
{
taskList.Add(ChangeStringValue(dataRow));
}
// await for proper async code, WaitAll is fin for console app.
await Task.WhenAll(taskList.ToArray());
// back to single thread - safe to update rows
foreach (var task in taskList)
{
task.Result.DataRow["Name"] = task.Result.Name;
}
}
private async Task<MyRowResult> ChangeStringValue(DataRow dataRow)
{
// on multiple threads - perform read only operations of rows
// which is safe as explicitly called out in the documentation
// "This type is safe for multithreaded read operations."
var id = (int)dataRow["ID"];
var newValue = await Task.Run(() => CreateNewValue(id));
return new MyRowResult { Name = newValue, DataRow = dataRow };
}
注意:
Parallel
类中的助手可以简化并行运行代码(如Parallel.ForEach
)。