3

我必须对数百万行数据运行一次 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)?

另外,如何找到最佳线程数?是不是越多越好?

4

2 回答 2

7

实体框架DbContextObjectContext不是线程安全的。所以你不应该在多个线程上使用它们。

尽管看起来您只是将实体传递给其他线程,但在涉及延迟加载时很容易出错。这意味着在幕后实体将回调到上下文以获取更多数据。

因此,我建议将实体列表转换为仅需要计算所需数据的特殊不可变数据结构列表。那些不可变的结构不应该回调到上下文中,也不应该改变。当你这样做时,将它们传递给其他线程进行计算是安全的。

于 2013-08-28T10:20:14.407 回答
2

除了 Steven 解决的实体框架的问题。

关于numberOfThreads

没有必要进行这种自我节流。发疯,让ThreadPool工作来为你维护一个任务队列并决定并发线程的数量。您不需要SplitUpClientsforeachDoCalc.

于 2013-08-28T10:34:56.237 回答