GC没有收集未引用的Context对象?

时间:2017-03-16 17:27:22

标签: c# entity-framework garbage-collection

使用此上下文

public class Context : System.Data.Entity.DbContext
{
    // For migration test
    public Context()
    { }


    public Context(DbConnection connection)
        : base(connection, false)
    {}
    public DbSet<Student> Students { get; set; }
    public DbSet<Standard> Standards { get; set; }
}

我正在运行此代码

for (int j = 0; j < 5; j++)
{

    for (int i = 0; i < 15; i++)
    {
        // ReSharper disable once ReturnValueOfPureMethodIsNotUsed
        (new Context(connection)).Students.ToList();
    }
    GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect();
    PrintUsedMemory();
}

垃圾收集器似乎无法收集EntityFramework使用的内存。我也尝试改变我创建上下文的方式,指定contextOwnConnection = true但没有任何改变。

有没有办法在没有明确处理上下文的情况下释放内存? (处理上下文的问题是,有一些相关的进程共享相同的上下文,所以我不知道何时可以处理上下文。)

修改
我尝试使用IDisposable的完整实现在上下文中创建一个包装器(也可以从析构函数中调用Dispose)但是从不调用析构函数。

EDIT2
PrintUsedMemory并不重要,因为您可以看到也没有使用任务管理器释放内存或等待OutOfMemoryException。 无论如何,这是代码

private static void PrintUsedMemory()
{
    long usedMemory = GetUsedMemory();

    Console.WriteLine("Used memory {0}", usedMemory);
}

private static long GetUsedMemory()
{
    Process proc = Process.GetCurrentProcess();
    long usedMemory = proc.WorkingSet64;
    return usedMemory;
}

编辑3
我开始使用@Evk建议的WeakReference进行相同的测试。

首次测试使用此代码,我在上下文创建期间指定连接(正常的sql连接)。

DbConnection connection = GetConnection();
connection.Open();

// create list of references
var errors = (new Context(connection)).Students.ToList();
var refs = errors.Select(c => new WeakReference(c)).ToList();
Console.WriteLine("refs count = {0}", refs.Count);
// collect
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Console.WriteLine(refs.All(c => !c.IsAlive) ? "all collected" : "something alive");
Console.WriteLine("done");

这是输出

refs count = 100
all collected
done

第二次测试我刚刚插入了一个用于创建引用的内容(这对于我对C#的了解很少是一个惊喜)

DbConnection connection = GetConnection();
connection.Open();

for (int i = 0; i < 5; i++)
{
    // create list of references
    var errors = (new Context(connection)).Students.ToList();
    var refs = errors.Select(c => new WeakReference(c)).ToList();
    Console.WriteLine("refs count = {0}", refs.Count);
    // collect
    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();
    Console.WriteLine(refs.All(c => !c.IsAlive) ? "all collected" : "something alive");

}

Console.WriteLine("done");

这是输出

refs count = 100
something alive
refs count = 100
something alive
refs count = 100
something alive
refs count = 100
something alive
refs count = 100
something alive
done

对象不会在带有连接的for内收集。

第三次测试我在没有指定连接的情况下尝试相同的代码

for (int i = 0; i < 5; i++)
{
    // create list of references
    var errors = (new Context()).Students.ToList();
    var refs = errors.Select(c => new WeakReference(c)).ToList();
    Console.WriteLine("refs count = {0}", refs.Count);
    // collect
    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();
    Console.WriteLine(refs.All(c => !c.IsAlive) ? "all collected" : "something alive");

}

Console.WriteLine("done");

这是输出

refs count = 100
all collected
refs count = 100
all collected
refs count = 100
all collected
refs count = 100
all collected
refs count = 100
all collected
done

在没有连接的情况下收集对象。

对我来说真的很奇怪

2 个答案:

答案 0 :(得分:3)

DBContext实现了IDisposable,因此使用它来释放资源。而不是:

(new Context(connection)).Students.ToList();

你可以使用:

Context ctx = new Context(connection);
ctx.Students.ToList();    
ctx.Dispose();

甚至更好,将它与using语句一起使用,并在循环中共享相同的上下文:

using(Context ctx = new Context(connection))
{
    for (int j = 0; j < 5; j++)
    {

        for (int i = 0; i < 15; i++)
        {
            // ReSharper disable once ReturnValueOfPureMethodIsNotUsed
            ctx.Students.ToList();
        }
    }
}

答案 1 :(得分:2)

让我们看看是否会在您的示例中收集内容,但不是使用PrintUsedMemory(使用您未提供的代码),我们将使用WeakReference类。此类IsAlive属性的文档:

  

获取当前引用的对象的指示   WeakReference对象已被垃圾收集。

要测试的代码很简单(我在这里使用了一些测试EF上下文):

class Program {
    public static void Main() {
        var errors = (new TestDBEntities()).Errors.ToList();
        // create list of references
        var refs = errors.Select(c => new WeakReference(c)).ToList();
        // collect
        GC.Collect();
        GC.WaitForPendingFinalizers();
        Console.WriteLine(refs.All(c => !c.IsAlive) ? "all collected" : "something alive");
        Console.WriteLine("done");            
    }
}

如果我们在发布模式下使用优化编译并在没有附加调试器的情况下运行 - 它将输出“all gather”,因此已收集所有实体。收集了Context,即使你没有明确地处理它 - 垃圾收集器调用它的终结器然后收集它。

现在你说你无法妥善处理你的上下文,因为你在其他地方使用它。这与您在示例中提供的方案完全不同,因为这意味着您可以在其他位置保留对上下文的引用。上下文依次保存对您在查询中收到的对象的引用。让我们修改示例以反映这一点:

class Program {
    // represents context reference you hold elsewhere
    static TestDBEntities _context = new TestDBEntities();
    public static void Main() {
        var errors = _context.Errors.ToList();
        // create list of references
        var refs = errors.Select(c => new WeakReference(c)).ToList();
        // collect
        GC.Collect();
        GC.WaitForPendingFinalizers();
        Console.WriteLine(refs.All(c => !c.IsAlive) ? "all collected" : "something alive");
        Console.WriteLine("done");
        Console.ReadKey();
    }                
}

现在它输出“活着的东西”,因为实际上实体不能被垃圾收集 - 上下文保存对它们的引用。可以做些什么?使用AsNoTracking()来阻止上下文保留它们:

class Program {
    // represents context reference you hold elsewhere
    static TestDBEntities _context = new TestDBEntities();
    public static void Main() {
        var errors = _context.Errors.AsNoTracking().ToList();
        // create list of references
        var refs = errors.Select(c => new WeakReference(c)).ToList();
        // collect
        GC.Collect();
        GC.WaitForPendingFinalizers();
        Console.WriteLine(refs.All(c => !c.IsAlive) ? "all collected" : "something alive");
        Console.WriteLine("done");
        Console.ReadKey();
    }                
}

此示例再次输出“all gather”,因为上下文不再保留对实体的引用(当然,您现在无法通过常规方式更新\删除它们,而不首先将它们附加到上下文中。)

当然,正如已多次重复一样 - 不要重复使用您的上下文或将其存储在全球范围内。创建新的上下文,执行一个所需的逻辑操作,将其丢弃。