昨天在博客園有園友問了我一個問題,是這樣的: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. }); } }
也有內存泄漏的問題。
總之,任何使用匿名方法的地方都要避免捕獲類的成員,當心內存泄漏。