日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > asp.net >内容正文

asp.net

5种避免C#.NET中因事件造成内存泄漏的技术

發(fā)布時(shí)間:2023/12/4 asp.net 43 豆豆
生活随笔 收集整理的這篇文章主要介紹了 5种避免C#.NET中因事件造成内存泄漏的技术 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

原文來(lái)自互聯(lián)網(wǎng),由長(zhǎng)沙DotNET技術(shù)社區(qū)編譯。?

5種避免C#.NET中事件造成的內(nèi)存泄漏的技術(shù)

C#(通常是.NET)中的事件注冊(cè)是內(nèi)存泄漏的最常見原因。至少?gòu)奈业慕?jīng)驗(yàn)來(lái)看。實(shí)際上,我從事件中看到了太多的內(nèi)存泄漏,因此 在代碼中看到?+ =將立即使我感到懷疑。

盡管事件很常見,但它們也很危險(xiǎn)。如果您不知道要查找的內(nèi)容,則事件很容易導(dǎo)致內(nèi)存泄漏。在本文中,我將解釋此問題的根本原因,并提供幾種最佳實(shí)踐技術(shù)來(lái)解決該問題。最后,我將向您展示一個(gè)簡(jiǎn)單的技巧,以找出您是否確實(shí)存在內(nèi)存泄漏。

了解內(nèi)存泄漏

在垃圾收集環(huán)境中,術(shù)語(yǔ)“內(nèi)存泄漏”有點(diǎn)反直覺。當(dāng)有一個(gè)垃圾收集器負(fù)責(zé)收集所有內(nèi)容時(shí),我的內(nèi)存如何泄漏?

答案是,在存在垃圾收集器(GC)的情況下,內(nèi)存泄漏表示有些對(duì)象仍在引用中,但實(shí)際上未被使用。由于已引用它們,因此GC將不會(huì)收集它們,并且它們將永久保存,占用內(nèi)存。

讓我們來(lái)看一個(gè)例子:

public class WiFiManager {public event EventHandler <WifiEventArgs> WiFiSignalChanged;// ... } public class MyClass {public MyClass(WiFiManager wiFiManager){wiFiManager.WiFiSignalChanged += OnWiFiChanged;}private void OnWiFiChanged(object sender, WifiEventArgs e){// do something} public void SomeOperation(WiFiManager wiFiManager) {var myClass = new MyClass(wiFiManager);myClass.DoSomething();//... myClass is not used again }

在此示例中,我們假設(shè)WiFiManager?在程序的整個(gè)生命周期中都處于活動(dòng)狀態(tài)。執(zhí)行SomeOperation之后,將創(chuàng)建MyClass的實(shí)例,并且不再使用它。程序員可能會(huì)認(rèn)為GC將收集它,但事實(shí)并非如此。所述WiFiManager保持在其事件MyClass的參考?WiFiSignalChanged和它引起了內(nèi)存泄漏。GC將永遠(yuǎn)不會(huì)收集MyClass

1.確保退訂

顯而易見的解決方案(盡管并非總是最簡(jiǎn)單的)是記住從事件中注銷事件處理程序。一種方法是實(shí)現(xiàn)IDisposable:

public class MyClass : IDisposable {private readonly WiFiManager _wiFiManager;public MyClass(WiFiManager wiFiManager){_wiFiManager = wiFiManager;_wiFiManager.WiFiSignalChanged += OnWiFiChanged;}public void Dispose(){_wiFiManager.WiFiSignalChanged -= OnWiFiChanged;}private void OnWiFiChanged(object sender, WifiEventArgs e){// do something}

當(dāng)然,您必須確保調(diào)用Dispose。如果您有WPF控件,一個(gè)簡(jiǎn)單的解決方案是退訂Unloaded事件。

public partial class MyUserControl : UserControl {public MyUserControl(WiFiManager wiFiManager){InitializeComponent();this.Loaded += (sender, args) => wiFiManager.WiFiSignalChanged += OnWiFiChanged;this.Unloaded += (sender, args) => wiFiManager.WiFiSignalChanged -= OnWiFiChanged;}private void OnWiFiChanged(object sender, WifiEventArgs e){// do something} }

優(yōu)點(diǎn)**:簡(jiǎn)單易讀的代碼。

缺點(diǎn):您很容易忘記取消訂閱,或者在所有情況下都不會(huì)取消訂閱,這將導(dǎo)致內(nèi)存泄漏。

注意:并非所有事件注冊(cè)都會(huì)導(dǎo)致內(nèi)存泄漏。注冊(cè)到將要過期的事件時(shí),不會(huì)發(fā)生內(nèi)存泄漏。例如,在WPF UserControl中,您可以注冊(cè)到Button的Click事件。這很好,并且不需要注銷,因?yàn)橛脩艨丶俏ㄒ灰迷揃utton的控件。如果沒有一個(gè)人引用用戶控件,那么也將沒有一個(gè)人引用按鈕,并且GC將同時(shí)收集兩者。

2.讓處理程序退訂

在某些情況下,您可能希望事件處理程序僅發(fā)生一次。在這種情況下,您將希望代碼自己退訂。當(dāng)事件處理程序是命名方法時(shí),它很容易:

public class MyClass {private readonly WiFiManager _wiFiManager;public MyClass(WiFiManager wiFiManager){_wiFiManager = wiFiManager;_wiFiManager.WiFiSignalChanged += OnWiFiChanged;}private void OnWiFiChanged(object sender, WifiEventArgs e){// do something_wiFiManager.WiFiSignalChanged -= OnWiFiChanged;} }

但是,有時(shí)您希望事件處理程序是lambda表達(dá)式。在這種情況下,以下是一種使自己退訂的有用技術(shù):

public class MyClass {public MyClass(WiFiManager wiFiManager){var someObject = GetSomeObject();EventHandler<WifiEventArgs> handler = null;handler = (sender, args) =>{Console.WriteLine(someObject);wiFiManager.WiFiSignalChanged -= handler;};wiFiManager.WiFiSignalChanged += handler;} }

在上面的示例中,lambda表達(dá)式非常有用,因?yàn)槟梢圆东@局部變量someObject,而使用處理程序方法則無(wú)法做到這一點(diǎn)。

優(yōu)點(diǎn):簡(jiǎn)單,易讀,只要您確定事件至少會(huì)觸發(fā)一次,就不會(huì)發(fā)生內(nèi)存泄漏。

缺點(diǎn):僅在需要處理一次事件的特殊情況下可用。

3.將弱事件與事件聚合器一起使用

在.NET中引用對(duì)象時(shí),您基本上會(huì)告訴GC該對(duì)象正在使用中,因此請(qǐng)不要收集它。有一種引用對(duì)象的方法,而無(wú)需實(shí)際說“我正在使用它”。這種參考稱為

弱參考

。您是說“我不需要它,但是如果它仍然存在,那么我會(huì)使用它”。在其他換句話說,如果某個(gè)對(duì)象僅被弱引用引用,則GC會(huì)收集該對(duì)象并釋放該內(nèi)存。這是使用.NET的WeakReference?類實(shí)現(xiàn)的。

我們可以通過多種方式使用它來(lái)防止內(nèi)存泄漏。一種流行的設(shè)計(jì)模式是使用事件聚合器[1]。這個(gè)概念是,任何人都可以訂閱?T類型的事件,任何人都可以發(fā)布?T類型的事件。因此,當(dāng)一個(gè)類發(fā)布事件時(shí),將調(diào)用所有訂閱的事件處理程序。事件聚合器使用WeakReference引用所有內(nèi)容。所以即使有物體提斯 訂閱事件,仍然可以對(duì)其進(jìn)行垃圾回收。

這是一個(gè)使用Prism?流行的事件聚合器(通過NuGet?Prism.Core提供[2])的示例[3]

public class WiFiManager {private readonly IEventAggregator _eventAggregator;public WiFiManager(IEventAggregator eventAggregator){_eventAggregator = eventAggregator;}public void PublishEvent(){_eventAggregator.GetEvent<WiFiEvent>().Publish(new WifiEventArgs());} public class MyClass {public MyClass(IEventAggregator eventAggregator){eventAggregator.GetEvent<WiFiEvent>().Subscribe(OnWiFiChanged);}private void OnWiFiChanged(WifiEventArgs args){// do something} public class WiFiEvent : PubSubEvent<WifiEventArgs> {// ... }

優(yōu)點(diǎn):?防止內(nèi)存泄漏,相對(duì)易于使用。

缺點(diǎn):

充當(dāng)所有事件的全局容器。任何人都可以訂閱任何人。這使得系統(tǒng)在過度使用時(shí)難以理解。沒有分離的關(guān)注點(diǎn)。

4.對(duì)常規(guī)事件使用弱事件處理程序

借助一些代碼技巧,可以將弱引用與常規(guī)事件一起使用。這可以通過幾種不同的方式來(lái)實(shí)現(xiàn)。這是使用Paul Stovell的WeakEventHandler[4]的示例:

public class MyClass {public MyClass(WiFiManager wiFiManager){wiFiManager.WiFiSignalChanged += new WeakEventHandler<WifiEventArgs>(OnWiFiChanged).Handler;}private void OnWiFiChanged(object sender, WifiEventArgs e){// do something} } public class WiFiManager {public event EventHandler<WifiEventArgs> WiFiSignalChanged;// ...public void SomeOperation(WiFiManager wiFiManager) {var myClass = new MyClass(wiFiManager);myClass.DoSomething();//... myClass is not used again }

我真的很喜歡這種方法,因?yàn)樵谖覀兊陌咐?#xff0c;發(fā)布者WiFiManager保留了標(biāo)準(zhǔn)的C#事件。這只是這種模式的一種實(shí)現(xiàn),但是實(shí)際上有很多方法可以解決。Daniel Grunwald寫了一篇[5]有關(guān)不同實(shí)現(xiàn)及其差異的文章。

優(yōu)點(diǎn):利用標(biāo)準(zhǔn)事件。簡(jiǎn)單。沒有內(nèi)存泄漏。關(guān)注點(diǎn)分離(與事件聚合器不同)。

缺點(diǎn):此模式的不同實(shí)現(xiàn)有一些細(xì)微之處和不同問題。該示例中的實(shí)現(xiàn)實(shí)際上創(chuàng)建了一個(gè) 注冊(cè)的包裝對(duì)象,該?包裝對(duì)象從未被GC收集。其他實(shí)現(xiàn)可以解決此問題,但還有其他問題,例如其他樣板代碼。在Daniel的文章中[6]了解有關(guān)此內(nèi)容的更多信息 。

WeakReference解決方案存在的問題

使用WeakReference意味著GC將能夠在可能的情況下收集訂閱類。但是,GC不會(huì)立即收集未引用的對(duì)象。就開發(fā)商而言,它是隨機(jī)的。因此,對(duì)于弱事件,您可能會(huì)在當(dāng)時(shí)不應(yīng)該存在的對(duì)象中調(diào)用事件處理程序。

事件處理程序可能會(huì)執(zhí)行無(wú)害的操作,例如更新內(nèi)部狀態(tài)。或者,它可能會(huì)更改程序狀態(tài),直到GC決定隨機(jī)收集某個(gè)時(shí)間為止。這種行為確實(shí)很危險(xiǎn)。在“弱事件模式是危險(xiǎn)的”中[7]對(duì)此進(jìn)行附加閱讀 。

5.在沒有內(nèi)存探查器的情況下檢測(cè)內(nèi)存泄漏

此技術(shù)是為了測(cè)試現(xiàn)有的內(nèi)存泄漏,而不是編碼模式以首先避免它們。

假設(shè)您懷疑某個(gè)類存在內(nèi)存泄漏。如果您有創(chuàng)建一個(gè)實(shí)例然后希望GC收集它的情況,則可以輕松地確定是否將收集您的實(shí)例或是否存在內(nèi)存泄漏。按著這些次序:

1.將終結(jié)器添加到您的可疑類中,并在其中放置一個(gè)斷點(diǎn):

1.在場(chǎng)景開始時(shí)添加以下要調(diào)用的魔術(shù)3行:

GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect();

這將迫使GC到目前為止收集所有未引用的實(shí)例(不在生產(chǎn)環(huán)境中使用),因此它們不會(huì)干擾我們的調(diào)試。

3.添加相同的3條魔術(shù)代碼行,以?方案之后運(yùn)行。請(qǐng)記住,該方案是創(chuàng)建并收集可疑對(duì)象的方案。

4.運(yùn)行有問題的方案。

在第1步中,我告訴您在類的終結(jié)器中放置一個(gè)斷點(diǎn)。第一個(gè)垃圾回收完成之后,您實(shí)際上應(yīng)該注意該斷點(diǎn)。否則,您可能會(huì)被廢棄舊實(shí)例感到困惑。需要注意的重要時(shí)刻是 您的方案之后調(diào)試器是否在Finalizer中停止 。

它還有助于在類的構(gòu)造函數(shù)中放置一個(gè)斷點(diǎn)。這樣,您可以計(jì)算創(chuàng)建次數(shù)和完成次數(shù)。如果觸發(fā)了終結(jié)器中的斷點(diǎn),則GC會(huì)收集您的實(shí)例,一切正常。如果沒有,則可能發(fā)生內(nèi)存泄漏。

這是我調(diào)試的一種方案,該方案使用了上一種技術(shù)中的WeakEventHandler,并且沒有內(nèi)存泄漏:

這是我使用常規(guī)事件注冊(cè)的另一種情況,它確實(shí)存在內(nèi)存泄漏:

摘要

總是讓我感到驚訝的是,C#看起來(lái)像是一種易于學(xué)習(xí)的語(yǔ)言,并且提供了一個(gè)提供訓(xùn)練平臺(tái)的環(huán)境。但實(shí)際上,還遠(yuǎn)遠(yuǎn)沒有做到。諸如使用事件之類的簡(jiǎn)單事情,可以由未經(jīng)培訓(xùn)的手輕松地將您的應(yīng)用程序變成一堆內(nèi)存泄漏。

至于在代碼中使用的正確模式,我認(rèn)為本文的結(jié)論應(yīng)該是,在所有情況下都沒有正確答案。提供的所有技術(shù),以及他們, 視情況而定是可行的解決方案。

原來(lái)這是一個(gè)相對(duì)較大的職位,但在此問題上,我仍然處于較高水平。這恰恰證明了在這些問題上存在多少深度,以及軟件開發(fā)如何永無(wú)止境。

有關(guān)內(nèi)存泄漏的更多信息,請(qǐng)查看我的文章查找,修復(fù)和避免C#.NET:8最佳實(shí)踐中的內(nèi)存泄漏[8]。從我自己的經(jīng)驗(yàn)和其他高級(jí).NET開發(fā)人員那里獲得的大量信息都為我提供了建議。它包括有關(guān)內(nèi)存分析器,非托管代碼的內(nèi)存泄漏,監(jiān)控內(nèi)存等信息。

我希望您在評(píng)論部分中留下一些反饋。并確保訂閱[9]博客并收到新帖子通知。

References

[1]?事件聚合器:?https://www.codeproject.com/Articles/812461/Event-Aggregator-Pattern
[2]?Prism.Core提供:?https://www.nuget.org/packages/Prism.Core/
[3]?示例:?https://www.nuget.org/packages/Prism.Core/
[4]?WeakEventHandler:?http://paulstovell.com/blog/weakevents
[5]?一篇:?https://www.codeproject.com/Articles/29922/Weak-Events-in-C
[6]?文章中:?https://www.codeproject.com/Articles/29922/Weak-Events-in-C
[7]?“弱事件模式是危險(xiǎn)的”中:?https://ladimolnar.com/2015/09/14/the-weak-event-pattern-is-dangerous/
[8]?查找,修復(fù)和避免C#.NET:8最佳實(shí)踐中的內(nèi)存泄漏:?https://michaelscodingspot.com/2019/01/03/find-fix-and-avoid-memory-leaks-in-c-net-8-best-practices/
[9]?訂閱:?https://michaelscodingspot.com/subscribe/

總結(jié)

以上是生活随笔為你收集整理的5种避免C#.NET中因事件造成内存泄漏的技术的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。