C#实体框架与线程处理

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方面)?

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


2
在创建线程时,特别是在创建它们时,请使用 using,例如 using (Entities db = new Entities()) { ... } - H H
2个回答

7

实体框架中的DbContextObjectContext不是线程安全的。因此,您不应该在多个线程上使用它们。

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

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


感谢您的回答:在上面的例子中,我应该创建一个DTO类来表示客户,并将这些列表传递给DoCalc方法吗?在每个线程中创建新的Entity实例是否可以? - woggles
是的,一个不可变的DTO包含所有需要进行计算的数据(但没有更多)。如果它代表一个客户端,你可能应该将其命名为ClientCalculationData。在线程上创建新实体是可以的,只要你不与对象上下文交互,但也许更清晰的做法是让计算输出不可变结构,然后将其转换回你希望在主线程上插入的实体。 - Steven

2

除了Steven所提到的Entity Framework问题外。

关于numberOfThreads

没有必要进行自我限制。发挥你的能力,让ThreadPool来完成它的工作,即为您维护任务队列并决定并发线程数。您无需对DoCalc中的SplitUpClientsforeach进行操作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接