昨天在博客园有园友问了我一个问题,是这样的:async
先是半个月前 @碧水青荷 童鞋的一句话“你们都说不要随便 Task.Run(()=>{})
这样写”,当时没有想太多,这句话并无引发我注意,只顾着回答他“不想在代码中加 async/await
该怎么作”的问题。code
而后这句话被 @裤兜 童鞋注意到,昨天问了我为何。我当时也很纳闷,Task.Run
在并行场景中很常见啊,为何你们会有不要随便使用的说法。很遗憾,我当时脑海里认为这种说法只是空穴来风,并无细究。orm
我有个习惯,就是下班路上在地铁上快速复盘一下今天发生的事情。当时这个问题恰好就在脑海里闪现了一下,“为何你们都说不要随便使用 Task.Run
”。忽然想起了多年前的一个晚上……哦,难道是“Ta”?blog
对,应该就是它,内存泄露,除了这个缘由我再也想不到其它缘由了。由于我隐约记得多年前我确实踩过一次这个坑,也多是两次。内存
没错,Task.Run
使用不当,一不留意就会有内存泄露的问题。资源
咱们先来看一段代码:博客
public class MyClass { private int _id; private Logger<MyClass> _logger; public MyClass(Logger<MyClass> logger) { _logger = logger; } public Task Foo(Logger<MyClass> logger) { return Task.Run(() => { _logger.LogInformation($"Executing job with ID {_id}"); // do sth. }); } }
在这段代码中,私有成员 _id
被 Task.Run
的匿名方法捕获使用,进而致使 MyClass
实例被引用。当外部使用完 MyClass
实例时,本该由 GC 回收的时候却发现它还被其它资源引用着,因此 GC 认为该实例不该用被回收,也就永远失去了被回收的机会。it
道理很简单,我就再也不用示例演示了。解决办法也很简单,想必不少人都知道,就是使用本地变量。io
public class MyClass { private int _id; private Logger<MyClass> _logger; public MyClass(Logger<MyClass> logger) { _logger = logger; } public Task Foo(Logger<MyClass> logger) { var localId = _id; return Task.Run(() => { _logger.LogInformation($"Executing job with ID {localId}"); // do sth. }); } }
经过将值分配给一个本地变量,类就没有成员被捕获,即避免了潜在的内存泄漏。form
内存泄漏问题在 Task.Run
身上发生很常见,容易被你们记住,容易提升警觉。其实不光是 Task.Run
,其它地方使用了匿名方法也一样要当心,好比这个示例:
public class MyClass { private int _id; private Logger<MyClass> _logger; private JobQueue _jobQueue; public MyClass(Logger<MyClass> logger, JobQueue jobQueue) { _logger = logger; _jobQueue = jobQueue; } public void Foo() { _jobQueue.EnqueueJob(() => { _logger.LogInformation($"Executing job with ID {_id}"); // do sth. }); } }
也有内存泄漏的问题。
总之,任何使用匿名方法的地方都要避免捕获类的成员,当心内存泄漏。