该篇内容由个人博客点击跳转同步更新!转载请注明出处!
存在问题
CancellationTokenSource
和Console.CancelKeyPress
的组合使用不当可能导致内存溢出,其实就是因为静态事件惹的祸。
模拟问题
最近项目中需要写一个控制台服务定时循环往表里插入数据,在每次数据插入的过程中不允许直接中断,类似Ctrl+C这种,需要退出的时候同时把正在执行的异步任务也退出。自然而然想到了CancellationTokenSource
和Console.CancelKeyPress
的组合
举个栗子(类似这样):
//类似方法
public async Task DemoTest()
{
CancellationTokenSource cts = new CancellationTokenSource();
Console.CancelKeyPress += (_, e) =>
{
e.Cancel = true;
cts.Cancel();
};
await Task.Run(() =>
{
//一些处理任务。。。。
}, cts.Token);
cts.Dispose();//释放
}
//循环调用
static async Task Main(string[] args)
{
Test t = new Test();
while (true)
{
await t.DemoTest();
}
Console.WriteLine("Hello World!");
}
按理说我每次用完释放内存应该就不会再加了,但事与愿为
截图中内存一直在增加,心想难道是GC回收不及时导致的?那我手动回收试下:
static async Task Main(string[] args)
{
Test t = new Test();
while (true)
{
await t.DemoTest();
//手动回收
GC.Collect();
GC.WaitForPendingFinalizers();
}
Console.WriteLine("Hello World!");
}
哦哟,这次看来貌似有点效果,增加的不是那么快了,而且GC可能回收还是存在一点延迟,我也以为应该可以了,放服务器跑了一晚上,第二天一看内存还是炸了。
继续找原因,看了下微软粑粑的手册
大致的意思就是如果当前
CancellationTokenSource
还在被使用的话,是不会被释放的。那再想一下,意思不就是CancellationTokenSource
调用了Dispose但Console.CancelKeyPress
还在使用导致的内存没释放呢?
发现问题
从使用方法上可以知道Console.CancelKeyPress
是一个事件,看下源码:
上面这段代码中,可以看到使用了一个静态的事件,而静态成员的生命周期是从AppDomain被加载开始,直到AppDomain被卸载,也就是说在通常情况下如果进程没被关闭,又忘记取消注册事件,那么事件包含的EventHandler委托所引用的对象会一直存在到进程结束为止,这就造成了内存泄露问题。这也是.NET中最常见的内存泄露问题的原因之一。
好了,现在知道问题了,原来是
CancellationTokenSource
释放了但Console.CancelKeyPress
因为是静态事件导致两个都没有释放。
解决问题
- 最简单,咋不用了,谁没事干跑个服务还上去按个Ctrl+C (这不和我过不去嘛,关我程序),chua chua chua 代码删掉,over~
- 直接把代码放到循环外层,这样就不会一直创建新对象了
- 就有点麻烦了,大致原因就是使用完之后把注册的事件取消。。。。关键怎么取消呢,直接用-=减去一个事件?试了一下,貌似没什么用,最好的还是自己实现一个事件的委托。具体的操作网上很多,这里就不说了。
总结
1、在释放CancellationTokenSource
的时候要先确保没有其它地方还在引用它
2、多了解静态事件的创建及释放
微信关注我哦!(转载注明出处)