爲什麼要小心使用 Task.Run

昨天在博客園有園友問了我一個問題,是這樣的:

先是半個月前 @碧水青荷 童鞋的一句話“大家都說不要隨便 Task.Run(()=>{}) 這樣寫”,當時沒有想太多,這句話並沒有引起我注意,只顧着回答他“不想在代碼中加 async/await 該怎麼做”的問題。

然後這句話被 @褲兜 童鞋注意到,昨天問了我爲什麼。我當時也很納悶,Task.Run並行場景中很常見啊,爲什麼大家會有不要隨便使用的說法。很遺憾,我當時腦海裏認爲這種說法只是空穴來風,並沒有細究。

我有個習慣,就是下班路上在地鐵上快速覆盤一下今天發生的事情。當時這個問題剛好就在腦海裏閃現了一下,“爲什麼大家都說不要隨便使用 Task.Run”。突然想起了多年前的一個晚上……哦,難道是“Ta”?

對,應該就是它,內存泄露,除了這個原因我再也想不到其它原因了。因爲我隱約記得多年前我確實踩過一次這個坑,也可能是兩次。

沒錯,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.
        });
    }
}

在這段代碼中,私有成員 _idTask.Run 的匿名方法捕獲使用,進而導致 MyClass 實例被引用。當外部使用完 MyClass 實例時,本該由 GC 回收的時候卻發現它還被其它資源引用着,所以 GC 認爲該實例不應用被回收,也就永遠失去了被回收的機會。

道理很簡單,我就不再用示例演示了。解決辦法也很簡單,想必很多人都知道,就是使用本地變量

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.
        });
    }
}

通過將值分配給一個本地變量,類就沒有成員被捕獲,即避免了潛在的內存泄漏。

內存泄漏問題在 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.
        });
    }
}

也有內存泄漏的問題。

總之,任何使用匿名方法的地方都要避免捕獲類的成員,小心內存泄漏

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章