我一直在调试我的程序,因为它会抛出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实例正确地进行垃圾回收? 为什么它不像现在这样工作?
答案 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();