在一次Demo中用到Task.Delay导致内存泄露
例子代码如下:
class Program
{
static void Main(string[] args)
{
TestAbc que = new TestAbc();
Task.Run(() =>
{
while (true)
{
var logs = que.Dequeue();
{
logs?.ToList().ForEach(x =>
{
if (x != null)
Console.WriteLine($"{(int)x.Text.LogLevel}{x.Text.Pos:000000}");
});
}
Task.Delay(100);
}
});
for (int i = 0; i < 10; i++)
{
var x = i;
Task.Run(() =>
{
for (int k = 0; k < 100; k++)
{
var rnd = new Random();
for (int j = 0; j < 30000; j++)
{
que.Enqueue(new LoggerInfo
{
Text = new LoggerInfoClass
{
Pos = j,
LogLevel = (LogLevel)x,
Text = $"{Thread.CurrentThread.ManagedThreadId}-{rnd.Next(9999999).ToString()}-{rnd.Next(9999999).ToString()}"
}
});
}
}
});
}
Thread.Sleep(3000000);
}
}
区区11个Task就导致内存疯狂飙升,2G,3G,4G…
把下面的11个task修改为Thread,一切OK,难道说微软开发的 Task有内存泄露问题?ooo,不应该是这样的啊。
再次review代码,发现有一个地方可疑。你注意到了吗??
Task.Delay()
Thread.Sleep()是同步延迟,Task.Delay()是异步延迟。
Thread.Sleep()会阻塞线程,Task.Delay()不会。
Thread.Sleep()不能取消,Task.Delay()可以。
Task.Delay()实质创建一个运行给定时间的任务,Thread.Sleep()使当前线程休眠给定时间。
反编译Task.Delay(),基本上讲它就是个包裹在任务中的定时器。
Task.Delay()和Thread.Sleep()最大的区别是Task.Delay()旨在异步运行,在同步代码中使用Task.Delay()是没有意义的;在异步代码中使用Thread.Sleep()是一个非常糟糕的主意。通常使用await关键字调用Task.Delay()。
难道是我没有等待 Task.Delay()的原因?我修改为等待试试。
Task.Delay(100).Wait();
再次编译尝试,好了,内存并没有再泄露。
再次看看对Task.Delay()的描述,原来Task.Delay在幕后使用了一个计时器线程,如果没有等待的话,后果很严重,就是线程池可能会耗尽线程,并且内存中大部分泄露也是计数器引起的。不过非常奇怪的是,如果没有下面的task数组,则并没有导致内存泄露。
看起来还是有些像bug。
开大最先线程池数
因为可能是池子太小导致的内存泄露bug,那我们开大线程池看啊看。
ThreadPool.SetMinThreads(300, 300);
ThreadPool.SetMaxThreads(300, 300);
嗯嗯,再次尝试运行,果然并没有内存泄露。oh,my god!
同步中慎用 非等待的异步Task,否则,你就瞧好吧!!!