C#NET4.0任务保持参考

时间:2012-03-06 02:41:35

标签: 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();
        }
    }
}

在我未提取的代码中:
Main 中的CancellationTokenSource用于取消整个操作 ConsumeFiles 中的那个用于取消子操作,以防在 ConsumeFiles 中的某些其他子操作中出现问题。

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

2 个答案:

答案 0 :(得分:6)

保留参考

的任务

var cts = new CancellationTokenSource();
ct.Register(() => cts.Cancel());

是你唯一的问题。

cts是您在CancellationTokenSource方法中创建的Main。在循环的每次迭代中,您创建一个 new CancellationTokenSource,然后在 new cts上调用Cancel注册回调。当您注册回调时,它会被传递到令牌的父级,该父级恰好是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
再过几分钟:

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

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

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

答案 1 :(得分:0)

可能问题可能出在给定行的闭包中:

Task.Factory.StartNew(() => { 
                 var b = dataSource; 
            }).Wait();

尝试更改为以下内容:

Task.Factory.StartNew(() => { 
                 var b = dataSource; 
                 .... 
                 b = dataSource = null; 
            }).Wait();