繁体   English   中英

C#:释放 memory 使用

[英]C#: Releasing memory usage

对于开箱即用的问题,我需要执行一个长期繁重的过程。 所以我把这个过程分成了多个子过程。 现在的问题是如何在每个 window 执行之前释放 memory。

用一个例子更容易解释。 让我们看一下这个伪代码。

1. Some earlier code to do other things
2. Do
3.     Raise a Task
4.     If raised-task > 1000
5.         Wait all raised task to finish
6.         Release Memory
7.     End If
8. Wile Something not relevant

有了这个想法,我开发了下一个方法,每次达到线程限制时都会执行该方法:

List<Task> LTask();
//It's not relevant, but this list is populate like
//var task = Task.Run(() => something());
//LTask.Add(task);

private void waitForAll()
{
    //Break point 1
    Task.WhenAll(LTasks).Wait();

    LTasks.Clear();
    LTasks = null;
    GC.Collect();
    GC.WaitForPendingFinalizers();

    //Break point 2
    LTasks = new List<Task>();
}

我预计 memory 在某些值附近会保持不变(有一些变化)。 我是说:

  1. 已达到线程限制
  2. memory 在 BreakPoint 1 中使用 Visual Studio 诊断工具的快照 --> 100MB
  3. 在 BreakPont 2 --> 100 MB 中使用 Visual Studio 诊断工具的 memory 使用快照。 第一个问题,为什么没有减少? 所有线程都完成了,我强制垃圾收集器执行。

下次达到限制并执行此代码时,如果我再次拍摄快照,memory 会不断增加:200、300、...

这是诊断工具的捕获。 每次到达断点 1 时拍摄奇数快照,并在断点 2 处拍摄偶数快照。

在此处输入图像描述

第二个问题,这将继续无限制地增加,直到抛出Out of memory Exception

最后一个问题,有什么办法可以解决这个问题,并发布memory?

更新 1:经过一些测试,感谢评论,我开发了一个测试代码来深入研究它。 必须涉及其他事情。 请看下一段代码。 memory 继续无限制地增加。

private List<Task> LTasks = new List<Task>();
private void manageThreadholdLimit()
{
    waitForAll();
    realeaseMemory();
}

private void waitForAll()
{
    Task.WhenAll(LTasks).Wait(); 
    LTasks.Clear();
    LTasks = null;  
}
private void realeaseMemory()
{   
    GC.Collect();
    GC.WaitForPendingFinalizers();

    LTasks = new List<Task>();
}
public void Main(){
    int i = 0;

    while (true)
    {
        i++;

        var task = Task.Run(() => Thread.Sleep(100));
        LTasks.Add(task);

        //Si hemos alcanzado el máximo de paralelismo, esperamos la conclusión
        if (i % 1000 == 0) manageThreadholdLimit();

    }
}

GC 收集在调试中略有不同,请参阅:( John Skeet 知道所有)所以我会在发布模式下运行它时分配日志以验证所有行为。

任何解决方案都将非常依赖于您的实际代码以及是否有非托管资源被访问。

也就是说,我以前必须处理过这样的问题,并且我以前曾以两种不同的方式“解决”过它。

一种解决方案

在执行实际工作的 class 中,让计数器在构造函数中递增并在终结器中递减,并等待计数器低于定义的阈值,重要的是再次运行 collect 以收集最终对象。

在继续之前请仔细检查 memory 的总消耗量,否则您可能会遇到 memory 异常。

现在这实际上会稍微增加您的 memory 消耗。 有关更多信息, 请参阅

另一种解决方案

通过使用 GC.GetTotalMemory() 甚至更好的性能计数器,有一个循环等待 memory 消耗下降,然后等待它下降。

如果您的资源没有被收集,这最终可能根本不做任何工作。

不保证在您取消引用 object 后GarbageCollector将立即运行。 事实上,它可能不会。 即使您想手动调用它以使用GC.Collect()进行测试,您也不能真正保证它会立即运行。 此外,经常调用 GC 也是有代价的。 可以使用 RAM(至少在具有大量 RAM 的较新机器中......)。 如果您的 memory 上升,并在较长时间后保持上升,您就会遇到问题。 这通常表明您还有其他一些 memory 问题。 也许你有泄漏?

如果我们谈论的是免费解决方案,您可以使用ProcessExplorerCLR Profiler来查找潜在的 memory 问题。 是一篇关于如何做到这一点的文章。

需要注意的事项:

  1. 尽快将对象的引用设置为null 你完成了清单吗? 将其值设置为null
  2. 将处理大量数据的方法拆分为更小的方法 - 即使您将某些内容设置为null ,GC 也不会在方法退出之前运行。
  3. 检查您是否在需要的地方正确实施了终结器。
  4. 确保您没有泄漏:检查您正在使用的对象是否没有从工作方法外部引用。 对事件处理程序和数据绑定要特别小心。

此外, Task.WhenAll()显然保留了对其所有“子”任务的引用。 如果您以相同的方法立即手动调用 GC,我认为它可能无法触及 memory,因为方法本身仍在“引用”它。

是一篇关于垃圾收集的 MSDN 文章。

我想我找到了一个解释,感谢@ScottChamberlain 的回答: Why Garbage Collector doesn't collect Tasks objects

我认为 memory 会增加,因为 TaskScheduler 是 static,并且仍然存储任务的引用,尽管任务已完成。 因此,引用的数量不断增加 memory,而不是每个任务本身的 memory 使用量。

我还没有弄清楚如何删除这些引用,但作为替代解决方案,我可以重构使用线程的代码。 例如,此代码是我问题中“更新 1”的替代方法。 memory 在 18mb 上保持稳定。

private static List<Thread> LThreads = new List<Thread>();
private static void manageThreadholdLimit()
{
    waitForAll();
    realeaseMemory();
}

private static void waitForAll()
{
    LThreads.ForEach(x => x.Join());
    LThreads.ForEach(x => x = null);
    LThreads.Clear();
    LThreads = null;
}
private static void realeaseMemory()
{


    GC.Collect();
    GC.WaitForPendingFinalizers();

    LTasks = new List<Task>();
    LThreads = new List<Thread>();
}
public static void Main(string[] args)
{
    int i = 0;

    while (true)
    {
        i++;

        var t = new Thread(() => Thread.Sleep(100));
        LThreads.Add(t);
        t.Start();

        if (i % 1000 == 0) manageThreadholdLimit();

    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM