如何释放Parallel.Task使用的内存?

jko*_*ian 9 c# task-parallel-library

我有一个程序进行内存密集型模拟.下面我写了一个小型控制台应用程序,它复制了我遇到的问题.

class Program {
    static void Main(string[] args) {
        var t = new Task(() => DoMemoryHog(20000000));
        t.Start();
        t.Wait();
        t.Dispose();
        t = null;
        GC.Collect();
        Console.WriteLine("Done");
        Console.ReadLine();
    }

    static void DoMemoryHog(int n) {
        ConcurrentBag<double> results = new ConcurrentBag<double>();

        Parallel.For(0, n, (i) => {
            results.Add(Math.Sqrt(i.GetHashCode()));
        });
    }
}
Run Code Online (Sandbox Code Playgroud)

当我运行程序时,我可以看到Windows任务管理器中使用的内存量增加,但是当任务完成(并显示"完成")时,内存不会恢复到原始级别,只会发生当我关闭应用程序时.

有没有人知道如何释放并行任务使用的内存,而主应用程序一直在运行?正如你所看到的,我已经尝试过处理它,将它的引用设置为null并手动运行垃圾收集器(我知道你不应该这样做).

Pol*_*ity 6

这是由于concurrentBag的性质(请参阅我之前关于ConcurrentBag的问题(ConcurrentBag中可能的memoryleak?)).

基本上,并发包将项目存储在本地线程上.如果您不使用这些项目,这些项目将保留在本地线程上.请检查以下示例:

    class TrackedItem
    {
        public TrackedItem()
        {
            Console.WriteLine("Constructor!");
        }
        ~TrackedItem()
        {
            Console.WriteLine("Destructor!");
        }
    }

    static void Main(string[] args)
    {
        Action allCollect = () =>
            {
                GC.Collect();
                GC.WaitForPendingFinalizers();
                GC.Collect();
            };

        // create a single item and loose it directly thereafter
        TrackedItem item = new TrackedItem();
        item = null;
        allCollect();
        // Constructor!, Destructor!

        ConcurrentBag<TrackedItem> bag = new ConcurrentBag<TrackedItem>();
        bag.Add(new TrackedItem());
        bag = null;
        allCollect();
        // Constructor!
        // Note that the destructor was not called since there is still a collection on the local thread

        Console.ReadLine();
    }
Run Code Online (Sandbox Code Playgroud)

concurrentBag使用ThreadLocal类,这样可以方便地为每个线程保存1个实例.在ThreadLocal中处理数据的预期方法是通过调用ThreadLocal上的Dispose方法(ThreadLocal实现IDisposable).这仅处理当前线程的数据.concurrentBag虽然没有处理它的ThreadLocals.相反,它依赖于所有正在使用的项目 - 或托管ThreadLocal的线程被处置.但是当你在ThreadPool中共享线程时,这可能非常讨厌.


obe*_*eak 5

我确定您的内存没有问题,.NET不会缩小使用的内存,因此将来可以使用。这样可以节省以后分配内存的时间。尝试在完成后重新运行循环,我确定内存不会增长。

因此,请尝试此操作,我会被结果所吸引!

class Program {
    static void Main(string[] args) {
        Process currentProcess = Process.GetCurrentProcess();
        for (int i = 0; i < 10; i++) {
            var t = new Task(() => DoMemoryHog(20000000));
            t.Start();
            t.Wait();
            t.Dispose();
            t = null;
            GC.Collect();
            Console.WriteLine("Done" +i);
            Console.WriteLine("Memory: " + GC.GetTotalMemory(false));
            Console.WriteLine("Paged: " + currentProcess.PagedMemorySize64);
            Console.WriteLine("-------------------------");
        }
        Console.ReadLine();
    }

    static void DoMemoryHog(int n) {
        ConcurrentBag<double> results = new ConcurrentBag<double>();

        Parallel.For(0, n, (i) =>
        {
            results.Add(Math.Sqrt(i.GetHashCode()));
        });
    }
}
Run Code Online (Sandbox Code Playgroud)

这两种方法用于打印出内存使用情况:

GC.GetTotalMemory();
currentProcess.PagedMemorySize64;
Run Code Online (Sandbox Code Playgroud)

我的输出是:

Done0
Memory: 480080992
Paged: 520753152
-------------------------
Done1
Memory: 480081512
Paged: 520753152
-------------------------
Done2
Memory: 480082160
Paged: 520753152
-------------------------
Done3
Memory: 480083476
Paged: 520753152
-------------------------
Done4
Memory: 480083496
Paged: 520753152
-------------------------
Done5
Memory: 480083516
Paged: 520753152
-------------------------
Done6
Memory: 480083536
Paged: 520753152
-------------------------
Done7
Memory: 480084204
Paged: 520753152
-------------------------
Done8
Memory: 480084204
Paged: 520753152
-------------------------
Done9
Memory: 480085500
Paged: 520753152
-------------------------
Run Code Online (Sandbox Code Playgroud)

据我所见,这里没有内存问题。清除对象,并适当地重用内存。问题解决了吗?

更新资料

专用字节行为:

专用字节数

如您所见,GC正在收集对象并释放内存,但是它当前并未释放它,因此可以分配新对象。