我一直在调试我的程序,因为它抛出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实例正确地进行垃圾回收?
为什么它不像现在这样工作?
这不是持有参考的任务
这个
var cts = new CancellationTokenSource();
ct.Register(() => cts.Cancel());
Run Code Online (Sandbox Code Playgroud)
是你唯一的问题.
cts是CancellationTokenSource在您的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 取消它们.不过,我不确定这是不是你要做的.