【问题标题】:Memory leak on unawaited tasks which throw exception?引发异常的未等待任务的内存泄漏?
【发布时间】:2019-01-22 07:40:26
【问题描述】:

下面是一个简单的控制台应用程序代码,它反复触发异步调用但不等待。被调用的函数抛出异常。 运行此控制台应用程序会产生以下结果:

  • 在调试器下的 VS 中 - 内存使用量持平。没有泄漏。
  • 直接运行 exe(在 vs 之外而不是在调试器下)- 内存不断增长到 gbs,最终 OOM 约为 4gb。

我不知道如何解释这些结果。任何帮助将不胜感激。

    static void Main(string[] args)
    {
        while (true)
        {
            Task.Run(() => RunMain());
        }
        Console.ReadLine();
    }
    static Exception ex = new Exception();
    private static void RunMain()
    {
        throw ex;
    }

编辑: 我主要对为什么连续抛出未观察到的异常时内存泄漏感兴趣。

【问题讨论】:

  • 很好奇。我现在不能自己测试这个,但是对于观察到的行为是否需要多个并发线程抛出相同且唯一的 Exception 对象实例?或者当每个任务创建自己的异常实例时也会发生同样奇怪的行为?
  • 我认为它在调试和发布模式下都会泄漏,即使您捕获了异常。但是,使用 附加调试器,您的代码运行速度要慢得多,因此它可能看起来很平坦,但实际上并非如此。如果您从诊断工具放大到进程内存。真奇怪。如果您收集第 0 代,它不会泄漏。 GC 无法正常工作。
  • @elgonzo - 不,这不是必需的。但我这样做只是为了消除创建新异常对象太多次的可能性。当每个任务都创建自己的异常时,会观察到相同的行为。
  • @M.kazemAkhgary - 你说得对,它在调试和发布模式下都会泄漏。关于减慢泄漏的调试器 - 这是可能的。当然,我的主要兴趣是弄清楚为什么会泄漏,而不是为什么在调试器下它会这么慢。

标签: c# .net task-parallel-library clr


【解决方案1】:

当您使用 Task.Run() 不断创建新任务时,您正在创建占用更多内存的新对象。我相信您忽略的是 Task 本身是一个对象的事实。

当您调用 Task.Run() 时,您将一个任务添加到线程池的队列中。我敢打赌,内存泄漏是由于新任务不断添加到线程池的队列中而线程池无法跟上它。

【讨论】:

  • 我认为主要问题是。为什么 GC 无法收集所有这些任务?
  • 我现在无法进行测试,但我猜这并不是 GC 没有收集任务,而是线程池无法跟上它多并发。线程池只有在队列中有东西一段时间后才会开始添加新的可用任务,然后一次只添加 1 个任务
  • GC.Collect(0) 这个程序没有泄漏。所以我认为它的 GC 没有弄清楚何时收集。 (手动调用可以采集)
  • 再考虑一下。我相信@TimRobinson 是正确的。这些任务的创建速度非常快,因此它们尚未运行(只是入队)。他们没有被 GCed,因为这些任务仍然存在。
  • 谢谢@TimRobinson。情况似乎如此。我用 sleep(10) 替换了 RunMain 中的 throw,并在内存增长方面得到了类似的结果。我不确定是否有办法限制线程队列大小。我注意到的另一个有趣的事情是,如果 GC 有一段时间没有启动,那么我们也会开始累积 UnobservedExceptions,这些异常会在 GC 收集启动时立即触发。这似乎也增加了内存压力,但我不确定.
猜你喜欢
  • 2013-05-20
  • 1970-01-01
  • 2018-09-11
  • 2012-07-24
  • 2018-11-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-03-20
相关资源
最近更新 更多