C#NET4.0任务保持参考

Aro*_*okh 3 c# reference task

我一直在调试我的程序,因为它抛出Out Of Memory异常.
剥离后,看起来任务正在保持参考,尽管它已经完成.
这是我的精简代码:

public class Program {
    static void Main(string[] args) {
        var cts = new CancellationTokenSource();
        ConsumeFiles(cts.Token);
    }

    public static void ConsumeFiles(CancellationToken ct) {
        while(true) {
            var dataSource = new Queue<byte[]>();
            for(int i = 0;i < 16;i++) {
                var block = new byte[4 * 1024 * 1024];
                for(int j = 0;j < block.Length;j++) block[j] = (byte)j;
                dataSource.Enqueue(block);
            }

            var cts = new CancellationTokenSource();
            ct.Register(() => cts.Cancel()); //Remove this line => No OOM

            Task.Factory.StartNew(() => { //Remove Task => No OOM
                var b = dataSource; //Remove this line => No OOM
            }).Wait();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在我的未经剥离的代码中:Main中
的CancellationTokenSource 用于取消整个操作.ConsumeFiles中 的一个用于取消子操作,以防ConsumeFiles中的某些其他子操作出错.

我需要做什么才能将旧的dataSource实例正确地进行垃圾回收?
为什么它不像现在这样工作?

Chr*_*ens 6

不是持有参考的任务

这个

var cts = new CancellationTokenSource();
ct.Register(() => cts.Cancel());
Run Code Online (Sandbox Code Playgroud)

是你唯一的问题.

ctsCancellationTokenSource在您的Main方法中创建的.在循环的每次迭代,你正在创建一个新的 CancellationTokenSource,然后注册,您拨打的回调Cancel CTS.当您注册回调时,它会被传递到令牌的父级,该父级恰好是其中的令牌源Main.每次注册一个,它都会被添加到数组中,根据需要增加它的大小.

因为main中的令牌源仍然在范围内,所以它不符合垃圾收集的条件,也不是它内部保留的回调数组.

以下是windbg的类型统计信息:

1-一两分钟后:

00787a68     1200        37560      Free
6d6913b8     1785        49980 System.Threading.CancellationCallbackInfo
6d64f468     1789        57248 System.Action
6d64eb28     1788        71520 System.Threading.CancellationTokenSource
6d6470cc       52    213910128 System.Byte[]
Total 6881 objects

2-几分钟后:

00787a68     1920        56496      Free
6d6913b8     2943        82404 System.Threading.CancellationCallbackInfo
6d64f468     2946        94272 System.Action
6d64eb28     2946       117840 System.Threading.CancellationTokenSource
6d6470cc       44    180355600 System.Byte[]
Total 11064 objects

3-几分钟:

00787a68     3269        91514      Free
6d6913b8     4975       139300 System.Threading.CancellationCallbackInfo
6d64f468     4976       159232 System.Action
6d64eb28     4978       199120 System.Threading.CancellationTokenSource
6d6470cc       10     37748856 System.Byte[]
Total 18465 objects

请注意,每次我在堆上运行一个stat,分配的byte[]更改数量(因为它们正在获取GC),其中CancellationTokenSource(新的一行)类型的数量不断增加,以及System.Action(您传递给的参数Register)和CancellationCallbackInfo(由Register()存储值创建的内部数据结构).

从您提供的代码中,如果要取消多个线程,则根本无法创建取消令牌源.您可以将token参数(ct)传递给要取消的其他任务.即使它是一个值类型,并且将被复制,指向源标记的指针仍然存在,您仍然可以使用cts.Cancel()from Main 取消它们.不过,我不确定这是不是你要做的.