关于Task.Run会有内存泄漏风险的终结
也不知道哪传来的Task.Run会有内存泄漏风险,开始在.NET圈子风靡,一时间大家都开始看看自己代码有没有存在这个问题。我也吃惊纳闷了一会,用自己的惯性思维思考了下.NET GC的基本工作原理:检查对象是否存在引用,不存在就会标记可回收,以及增加代数,并且会根据当前托管堆的内存使用情况来决定是否执行这些工作。
思来想去这个和异步有撒关系?按网上的说法还不仅仅是Task.Run,所有的异步方法都存在这个问题,咱.NET的GC有这么弱智?处理不了异步已过期对象?
参考大佬们的测试代码自己鼓捣一番,好家伙,还真回收不了?
引用:https://www.cnblogs.com/huangxincheng/p/14107100.html
看下这个测试代码,好像没毛病?错了,有毛病,最大的问题就是关于托管堆内存的使用情况,托管堆内存毫无压力的时候,GC才懒得理你。
优化他的测试代码,加点内存压力进去:
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace GCCollectIntExample
{
class Program
{
static void Main(string[] args)
{
Test();
Console.ReadLine();
}
static void Test()
{
for (int i = 0; i < 1000; i++)
{
var myClass = new MyClass();
myClass.Foo();
}
}
}
public class MyClass
{
private int _id = 10;
private List<Version> Versions { get; } = new List<Version>();
public MyClass()
{
for (int i = 0; i < 100000; i++)
{
Versions.Add(new Version());
}
}
public Task Foo()
{
return Task.Run(() =>
{
Console.WriteLine($"Task.Run is executing with ID {_id}");
});
}
private static int Counter = 0;
private static readonly object _locker = new object();
~MyClass()
{
lock (_locker)
{
Counter++;
}
Console.WriteLine($"Finalized, counter: {Counter}");
}
}
}
最后能得到输出:
Finalized, counter: 993
Finalized, counter: 994
Finalized, counter: 995
Task.Run is executing with ID 10
Task.Run is executing with ID 10
Task.Run is executing with ID 10
Finalized, counter: 996
Finalized, counter: 997
Finalized, counter: 998
Task.Run is executing with ID 10
释放掉了998个对象,每次执行这个数量可能不一致,完全是根据内存使用情况来的。
所以得出结论:放心大胆的用Task.Run,微软这点都考虑不到的话,那NETER还有什么前途。真正需要关注的是微软官方给出的内存泄漏注意点:https://docs.microsoft.com/zh-cn/aspnet/core/performance/memory?view=aspnetcore-5.0。