我必须对数百万行数据运行一次C#计算,然后将结果保存在另一个表中。几年来我一直没有使用C#进行线程处理。我正在使用.NET v4.5和EF v5。
原始代码类似于以下内容:
public static void Main()
{
Stopwatch sw = new Stopwatch();
sw.Start();
Entities db = new Entities();
DoCalc(db.Clients.ToList());
sw.Stop();
Console.WriteLine(sw.Elapsed);
}
private static void DoCalc(List<Client> clients)
{
Entities db = new Entities();
foreach(var c in clients)
{
var transactions = db.GetTransactions(c);
var result = calulate(transactions); //the actual calc
db.Results.Add(result);
db.SaveChanges();
}
}
这是我对多线程的尝试:
private static int numberOfThreads = 15;
public static void Main()
{
Stopwatch sw = new Stopwatch();
sw.Start();
Entities db = new Entities();
var splitUpClients = SplitUpClients(db.Clients());
Task[] allTasks = new Task[numberOfThreads];
for (int i = 0; i < numberOfThreads; i++)
{
Task task = Task.Factory.StartNew(() => DoCalc(splitupClients[i]));
allTasks[i] = task;
}
Task.WaitAll(allTasks);
sw.Stop();
Console.WriteLine(sw.Elapsed);
}
private static void DoCalc(List<Client> clients)
{
Entities db = new Entities();
foreach(var c in clients)
{
var transactions = db.GetTransactions(c);
var result = calulate(transactions);
db.Results.Add(result);
db.SaveChanges();
}
}
//splits the list of clients into n subgroups
private static List<List<Client>> SplitUpClients(List<Client> clients)
{
int maxPerGroup = (int)Math.Ceiling((double)clients.Count() / numberOfThreads);
return ts.Select((s, i) => new { Str = s, Index = i }).
GroupBy(o => o.Index / maxPerGroup, o => o.Str).
Select(coll => coll.ToList()).
ToList();
}
我的问题是:
这是安全且正确的方法吗?是否存在任何明显的缺点(尤其是在EF方面)?
另外,如何找到最佳线程数?是越多越好吗?
最佳答案
Entity Framework DbContext和ObjectContext类是而不是线程安全的。因此,您不应在多个线程上使用它们。
尽管似乎您只是将实体传递给其他线程,但是在涉及延迟加载时,很容易出错。这意味着在幕后,实体将回调到上下文以获取更多数据。
因此,我建议将实体列表转换为仅需要计算所需数据的特殊不可变数据结构列表。那些不可变的结构应该不必调回上下文,也不能更改。执行此操作时,将它们传递给其他线程进行计算将是安全的。