我有一个DbContext,其数据集大于&n; 20M记录,必须转换为不同的数据格式。因此,我将数据读入内存,执行一些任务然后处理DbContext。代码工作正常,但过了一段时间我得到OutOfMemoryExceptions。我已经能够将其缩小到下面的代码片段,在那里我检索2M记录,然后释放它们并再次获取它们。第一次检索工作得很好,第二次检索抛出异常。
// first call runs fine
using (var dbContext = new CustomDbContext())
{
var list = dbContext.Items.Take(2000000).ToArray();
foreach (var item in list)
{
// perform conversion tasks...
item.Converted = true;
}
}
// second call throws exception
using (var dbContext = new CustomDbContext())
{
var list = dbContext.Items.Take(2000000).ToArray();
foreach (var item in list)
{
// perform conversion tasks...
item.Converted = true;
}
}
GC不应该自动释放第一个使用块中分配的所有内存,这样第二个块应该像第一个块一样运行吗?
在我的实际代码中,我不会一次检索200万条记录,而是在每次迭代中检索0到30K之间的记录。然而,大约15分钟后,我的内存耗尽,尽管所有对象都应该被释放。
答案 0 :(得分:1)
我怀疑你遇到了LOH。可能你的对象比threashold更大,他们到达那里,因此默认情况下GC没有帮助。
试试这个:https://www.simple-talk.com/dotnet/.net-framework/large-object-heap-compaction-should-you-use-it/
并查看您的例外是否消失。
即。在第一和第二部分之间添加:
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
GC.Collect();
答案 1 :(得分:0)
IEnumerable有GetEnumerator()所以你可以尝试这个来避免.ToArray()或.ToList()如果你只是想阅读就不需要:
// first call
using (var dbContext = new CustomDbContext())
{
foreach (var item in dbContext.Items.Take(2000000))
{
// perform conversion tasks...
item.Converted = true;
}
}
// second call
using (var dbContext = new CustomDbContext())
{
foreach (var item in dbContext.Items.Take(2000000))
{
// perform conversion tasks...
item.Converted = true;
}
}
答案 2 :(得分:0)
运行GC无助于您,您必须在不同的上下文中运行每次迭代。并处理你的背景。
// ID is your primary key
long startID = 0;
while(true){
using(var db = new CustomDbContext()){
var slice = db.Items.Where(x=>x.ID > startID)
.OrderBy(x=>x.ID)
.Take(1000).ToList();
// stop if there is nothing to process
if(!slice.Any())
break;
foreach(var item in slice){
// your logic...
item.Converted = true;
}
startID = slice.Last().ID;
}
}
如果你想更快地处理这些事情,替代方法就是并行运行切片......
替代方法
我建议在100x100中使用分割片,然后我可以并行处理100片100片。
您可以随时轻松自定义切片以满足您的速度需求。
public IEnumerable<IEnumerable<T>> Slice(IEnumerable<T> src, int size){
while(src.Any()){
var s = src.Take(size);
src = src.Skip(size);
yield return s;
}
}
long startID = 0;
while(true){
using(var db = new CustomDbContext()){
var src = db.Items.Where(x=>x.ID > startID)
.OrderBy(x=>x.ID)
.Take(10000).Select(x=>x.ID).ToList();
// stop if there is nothing to process
if(!src.Any())
break;
Parallel.ForEach(src.Slice(100), slice => {
using(var sdb = new CustomDbContext()){
foreach(var item in sdb.Items.Where(x=> slice.Contains(x.ID)){
item.Converted = true;
}
}
} );
startID = src.Last();
}
}
答案 3 :(得分:0)
重构后,内存被释放。我不知道为什么,但它确实有效。
private static void Debug()
{
var iteration = 0;
while(true)
{
Console.WriteLine("Iteration {0}", iteration++);
Convert();
}
}
private static void Convert()
{
using (var dbContext = new CustomDbContext(args[0]))
{
var list = dbContext.Items.Take(2000000).ToList();
foreach (var item in list)
{
item.Converted = true;
}
}
}
当我将Convert()的内容移动到Debug()中的while循环时,抛出OutOfMemoryExceptions。
private static void Debug()
{
var iteration = 0;
while(true)
{
Console.WriteLine("Iteration {0}", iteration++);
using (var dbContext = new CustomDbContext(args[0]))
{
// OutOfMemoryException in second iteration
var list = dbContext.Items.Take(2000000).ToList();
foreach (var item in list)
{
item.Converted = true;
}
}
}
}