為什么要小心使用Task.Run
昨天在博客園有園友問了我一個問題,是這樣的:

先是半個月前 @碧水青荷 童鞋的一句話“大家都說不要隨便?Task.Run(()=>{})?這樣寫”,當(dāng)時沒有想太多,這句話并沒有引起我注意,只顧著回答他“不想在代碼中加?async/await?該怎么做”的問題。
然后這句話被 @褲兜 童鞋注意到,昨天問了我為什么。我當(dāng)時也很納悶,Task.Run?在并行場景中很常見啊,為什么大家會有不要隨便使用的說法。很遺憾,當(dāng)時我腦海里認(rèn)為這種說法只是空穴來風(fēng),并沒有細究。
我有個習(xí)慣,就是下班路上在地鐵上快速復(fù)盤一下今天發(fā)生的事情。當(dāng)時這個問題剛好就在腦海里閃現(xiàn)了一下,“為什么大家都說不要隨便使用?Task.Run”。突然想起了多年前的一個晚上……哦,難道是“Ta”?
對,應(yīng)該就是它,內(nèi)存泄露,除了這個原因我再也想不到其它原因了。因為我隱約記得多年前我確實踩過一次這個坑,也可能是兩次。
沒錯,Task.Run?使用不當(dāng),一不留意就會有內(nèi)存泄露的問題。
我們先來看一段代碼:
public?class?MyClass
{
private?int _id;
private Logger_logger;
public?MyClass(Loggerlogger )
{
_logger = logger;
}
public Task Foo(Loggerlogger )
{
return Task.Run(() =>
{
_logger.LogInformation($"Executing job with ID {_id}");
// do sth.
});
}
}
在這段代碼中,私有成員?_id?被?Task.Run?的匿名方法捕獲使用,進而導(dǎo)致?MyClass?實例被引用。當(dāng)外部使用完?MyClass?實例時,本該由 GC 回收的時候卻發(fā)現(xiàn)它還被其它資源引用著,所以 GC 認(rèn)為該實例不應(yīng)用被回收,也就永遠失去了被回收的機會。
道理很簡單,我就不再用示例演示了。解決辦法也很簡單,想必很多人都知道,就是使用本地變量。
public?class?MyClass
{
private?int _id;
private Logger_logger;
public?MyClass(Loggerlogger )
{
_logger = logger;
}
public Task Foo(Loggerlogger )
{
var localId = _id;
return Task.Run(() =>
{
_logger.LogInformation($"Executing job with ID {localId}");
// do sth.
});
}
}
通過將值分配給一個本地變量,類就沒有成員被捕獲,即避免了潛在的內(nèi)存泄漏。
內(nèi)存泄漏問題在?Task.Run?身上發(fā)生很常見,容易被大家記住,容易提高警覺。其實不光是?Task.Run,其它地方使用了匿名方法也同樣要小心,比如這個示例:
public?class?MyClass
{
private?int _id;
private Logger_logger;
private JobQueue _jobQueue;
public?MyClass(Loggerlogger, JobQueue jobQueue )
{
_logger = logger;
_jobQueue = jobQueue;
}
public?void?Foo()
{
_jobQueue.EnqueueJob(() =>
{
_logger.LogInformation($"Executing job with ID {_id}");
// do sth.
});
}
}
也有內(nèi)存泄漏的問題。
總之,任何使用匿名方法的地方都要避免捕獲類的成員,小心內(nèi)存泄漏。
-
精致碼農(nóng)
帶你洞悉編程與架構(gòu)
↑長按圖片識別二維碼關(guān)注,不要錯過網(wǎng)海相遇的緣分
