的run代码_小心使用 Task.Run 续篇
關于前兩天發布的文章:為什么要小心使用 Task.Run,對文中演示的示例到底會不會導致內存泄露,給很多人帶來了疑惑。這點我必須向大家道歉,是我對導致內存泄漏的原因沒描述和解釋清楚,也沒用實際的示例證實,是我的錯。
但是,文中示例演示的 Task.Run 捕獲類成員的情況,確實會有內存泄漏的風險,我將在本文演示給大家看。
如果一個對象(或數據)不需要再使用了,但依然還一直占據內存空間,則視為內存泄漏。這一點大家觀點是一致的吧,那如何來檢測對象有沒有被回收呢?
我們知道,在 C# 中,實例對象被釋放回收,必然會執行析構函數。所以我們可以對一個類重寫其析構函數,如果該類的實例對象使用完后,強制執行 GC 回收,其析構函數依然不被執行,則說明 GC 沒有回收該對象。若 GC 后面一直不回收這個對象,則說明存在內存泄漏。
手動強制執行 GC 回收的代碼如下:
GC.Collect();GC.WaitForPendingFinalizers();GC.Collect();這三句代碼可以確保 GC 把所有能搜索到的可回收對象清理干凈。注意:不推薦在生產環境這樣寫。
我們還是用 為什么要小心使用 Task.Run 這篇文章用到的示例,只是為了測試稍加修改了一下:
class?Program{ static?void?Main(string[] args) { Test(); // 對不需要再使用的資源強制回收 GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); // 程序保活 while (true) { Thread.Sleep(100); } } static?void?Test() { var myClass = new MyClass(); myClass.Foo(); // 到這,myClass對象不需要再使用了 }}public?class?MyClass{ private?int _id; private List _list; public Task Foo() { return Task.Run(() => { Console.WriteLine($"Task.Run is executing with ID {_id}"); Thread.Sleep(100); // 模板耗時操作 }); } ~MyClass() { Console.WriteLine("MyClass instance has been colleted."); }}我們在 myClass 對象使用完后,手動強制執行 GC 回收,運行結果如下:
我們看到 MyClass 的析構函數一直沒有執行,也就意味著它的實例一直沒有被回收。
現在我們修改 MyClass 類的 Foo 方法,改用本地(局部)變量試一試:
...public Task Foo(){ var localId = _id; return Task.Run(() => { Console.WriteLine($"Task.Run is executing with ID {localId}"); });}...再運行看看效果:
這次我們可以看到,MyClass 的析構函數執行了,說明實例對象被回收了。
前后唯一區別是,前者在 Task.Run 的匿名方法中捕獲了類的成員,而后者使用了本地變量。前者出現了內存泄漏,后者避免了內存泄漏。
所以,在 Task.Run 的匿名方法中捕獲類的成員,確實有可能導致內存泄漏(注意是有可能而不是一定)。
那背后的原因是什么呢?我在上一篇文章是這樣解釋的:
私有成員 _id 被 Task.Run 的匿名方法捕獲使用,進而導致 MyClass 實例被引用。當外部使用完 MyClass 實例時,本該由 GC 回收的時候卻發現它還被其它資源引用著,所以 GC 認為該實例不應該被回收,也就可能永遠失去了被回收的機會。
這個解釋有很大的問題,至少給廣大讀者帶來了兩大疑惑:
感謝善于思考提出疑惑的讀者們,為你們點贊。
這兩大疑惑該如何解釋?后半部分我還沒寫完,大家可以先思考一下,我將在下一篇給大家解惑,望大家見諒。當然,我的解釋也不一定會是對的,希望大家帶著懷疑的態度和批判性思維來看我的文章,也請大家分享自己的理解和觀點。
-
精致碼農
帶你洞悉編程與架構
點頭像關注,不錯過網海相遇的緣分[比心]
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的的run代码_小心使用 Task.Run 续篇的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: HTML+CSS+JS实现 ❤️制作lo
- 下一篇: java获取500错误_HTTP 500