国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > C# > 正文

C#中event內(nèi)存泄漏總結(jié)

2019-10-29 21:05:54
字體:
供稿:網(wǎng)友

內(nèi)存泄漏是指:當(dāng)一塊內(nèi)存被分配后,被丟棄,沒有任何實例指針指向這塊內(nèi)存, 并且這塊內(nèi)存不會被GC視為垃圾進行回收。這塊內(nèi)存會一直存在,直到程序退出。C#是托管型代碼,其內(nèi)存的分配和釋放都是由CLR負(fù)責(zé),當(dāng)一塊內(nèi)存沒有任何實例引用時,GC會負(fù)責(zé)將其回收。既然沒有任何實例引用的內(nèi)存會被GC回收,那么內(nèi)存泄漏是如何發(fā)生的?

內(nèi)存泄漏示例

為了演示內(nèi)存泄漏是如何發(fā)生的,我們來看一段代碼

class Program { static event Action TestEvent; static void Main(string[] args) {  var memory = new TestAction();  TestEvent += memory.Run;  OnTestEvent();  memory = null;  //強制垃圾回收  GC.Collect(GC.MaxGeneration);  Console.WriteLine("GC.Collect");  //測試是否回收成功  OnTestEvent();  Console.ReadLine(); } public static void OnTestEvent() {  if (TestEvent != null) TestEvent();  else Console.WriteLine("Test Event is null"); } class TestAction  {  public void Run() {   Console.WriteLine("TestAction Run.");  } }}

該例子中,memory.run訂閱了TestEvent事件,引發(fā)事件后,會在屏幕上看到 TestAction Run。當(dāng)memory =null 后,memory原來指向的內(nèi)存就沒有任何實例再引用該塊內(nèi)存了,這樣的內(nèi)存就是待回收的內(nèi)存。GC.Collect(GC.MaxGeneration)語句會強制執(zhí)行一次垃圾回收,再次引發(fā)事件,發(fā)現(xiàn)屏幕上還是會顯示TestAction Run。該內(nèi)存沒有被GC回收,這就是內(nèi)純泄漏。這是由TestEvent+=memory.Run語句引起的,當(dāng)GC.Collect執(zhí)行的時候,當(dāng)他看到該塊內(nèi)存還有TestEvent引用,就不會進行回收。但是該內(nèi)存已經(jīng)是“無法到達”的了,即無法調(diào)用該塊內(nèi)存,只有在引發(fā)事件的時候,才能執(zhí)行該內(nèi)存的Run方法。這顯然不是我想要的效果,當(dāng)memory = null執(zhí)行時,我希望該內(nèi)存在GC執(zhí)行時被回收,并且當(dāng)TestEvent被引發(fā)時,Run方法不會執(zhí)行,因為我已經(jīng)把該內(nèi)存“解放”了。

這里有一個問題,就是C#中如何“釋放”一塊內(nèi)存。像C和C++這樣的語言,內(nèi)存的聲明和釋放都是開發(fā)人員負(fù)責(zé)的,一旦內(nèi)存new了出來,就要delete,不然就會造成內(nèi)存泄漏。這更靈活,也更麻煩,一不小心就會泄漏,忘記釋放、線程異常而沒有執(zhí)行釋放的代碼...有手動分配內(nèi)存的語言就有自動分配和釋放的語言。最開始使用垃圾回收的語言是LISP,之后被用在Java和C#等托管語言中。像C#,CLR負(fù)責(zé)內(nèi)存的釋放,當(dāng)程序執(zhí)行一段時間后,CLR檢測到垃圾內(nèi)存已經(jīng)值得進行一次垃圾回收時,會執(zhí)行垃圾回收。至于如何判定一塊內(nèi)存是否為垃圾內(nèi)存,比較著名的是計數(shù)法,即有一個實例引用了該內(nèi)存后,就在該內(nèi)存的計數(shù)上+1,改實例取消了對該內(nèi)存的引用,計數(shù)就-1,當(dāng)計數(shù)為0時,就被判定為垃圾。該種方法的問題是對循環(huán)引用束手無策,如A的某個字段引用了B,而B的某個字段引用了A,這樣A和B的技術(shù)都不會降到0。CLR改用的方法是類似“標(biāo)記引用法”(我自己的命名):在執(zhí)行GC時,會掛起全部線程,并將托管堆中所有的內(nèi)存都打上垃圾的標(biāo)記,之后遍歷所有可到達的實例,這些實例如果引用了托管堆的內(nèi)存,就將該內(nèi)存的標(biāo)記由垃圾變?yōu)楸灰谩.?dāng)遇到A和B相互引用的時候,如果沒有其他實例引用A或者B,雖然A和B相互引用,但是A和B都是不可到達的,即沒辦法引用A或者B,則A和B都會被判定為垃圾而被回收。講解了這么一大堆,目的就是要說,在C#中,你想要釋放一塊內(nèi)存,你只要讓該塊內(nèi)存沒有任何實例引用他,就可以了。那么當(dāng)執(zhí)行memory = null后,除了對TestEvent的訂閱,沒有任何實例再引用了該塊內(nèi)存,那么為什么訂閱事件會阻止內(nèi)存的釋放?

我們來看看TestEvent+=memory.Run()這句話都干了什么。我們利用IL反編譯上面的dll,可以看到

IL_0000: nopIL_0001: newobj  instance void EventLeakMemory.Program/TestAction::.ctor()IL_0006: stloc.0IL_0007: ldloc.0IL_0008: ldftn  instance void EventLeakMemory.Program/TestAction::Run()IL_000e: newobj  instance void [mscorlib]System.Action::.ctor(object, native int)IL_0013: call  void EventLeakMemory.Program::add_TestEvent(class [mscorlib]System.Action)...//其他部分

關(guān)鍵在5-7行。第5和6行,聲明了一個System.Action型的委托,參數(shù)為TestAction.Run方法,第七行,執(zhí)行了Program.add_TestEvent方法,參數(shù)是上面聲明的委托。也就是說+=操作符相當(dāng)于執(zhí)行了Add_TestEvent(new Action(memory.Run)),就是這個new Action包含了對memory指向的內(nèi)存的引用。而這個引用在CLR看來是可達的,可以通過引發(fā)事件來調(diào)用該內(nèi)存。

解決辦法

我們已經(jīng)找到了內(nèi)存泄漏的元兇,就是訂閱事件時,隱式聲明的匿名委托對內(nèi)存的引用。該問題的解決辦法是使用一種和普通的引用不同的方式來引用方法的實例對象:該引用不會影響垃圾回收,不會在GC時被判定為對該內(nèi)存的引用,也就是“弱引用”。C#中,絕大部分的類型都是強引用。如何實現(xiàn)弱引用?來看一個例子:

static void Main(string[] args){ var obj = new object(); var gcHandle = GCHandle.Alloc(obj, GCHandleType.Weak); Console.WriteLine("gcHandle.Target == null is :{0}", gcHandle.Target == null); obj = null; GC.Collect(); Console.WriteLine("GC.Collect"); Console.WriteLine("gcHandle.Target == null is :{0}", gcHandle.Target == null); Console.ReadLine();}

當(dāng)執(zhí)行GC。Collect后,gcHandle.Target == null 由false 變成了true。這個gcHandle就是obj的一個弱引用。這個類的詳細介紹見 GCHandle 。比較關(guān)鍵的是GCHandle.Alloc方法的第二個參數(shù),該參數(shù)接受一個枚舉類型。我使用的是GCHandleType.Weak,表明該引用是個弱引用。利用這個方法,就可以封裝一個自己的WeakReference類,代碼如下

public class WeakReference<T> where T : class { private GCHandle handle; public WeakReference(T obj) {  if (obj == null) return;  handle = GCHandle.Alloc(obj, GCHandleType.Weak); } /// <summary> /// 引用的目標(biāo)是否還存活(沒有被GC回收) /// </summary> public bool IsAlive {  get {   if (handle == default(GCHandle)) return false;   return handle.Target != null;  } } /// <summary> /// 引用的目標(biāo) /// </summary> public T Target {  get {   if (handle == default(GCHandle)) return null;   return (T)handle.Target;  } }}

利用該類,就可以寫一個自己的弱事件封裝器。

public class WeakEventManager<T> { private Dictionary<Delegate, WeakReference<T>> delegateDictionary; public WeakEventManager() {  delegateDictionary = new Dictionary<Delegate, WeakReference<T>>(); } /// <summary> /// 訂閱 /// </summary> public void AddHandler(Delegate handler) {  if (handler != null)   delegateDictionary[handler] = new WeakReference<T>(handler); } /// <summary> /// 取消訂閱 /// </summary> public void RemoveHandler(Delegate handler) {  if (handler != null)   delegateDictionary.Remove(handler); } /// <summary> /// 引發(fā)事件 /// </summary> public void Raise(object sender, EventArgs e) {  foreach (var key in delegateDictionary.Keys) {   if (delegateDictionary[key].IsAlive)    key.DynamicInvoke(sender, e);   else    delegateDictionary.Remove(key);  } }}

最后,就可以像下面這樣定義自己的事件了

public class TestEventClass { private WeakEventManager<Action<object, EventArgs>> _testEvent = new WeakEventManager<Action<object, EventArgs>>(); public event Action<object, EventArgs> TestEvent {  add { _testEvent.AddHandler(value); }  remove { _testEvent.RemoveHandler(value); } } protected virtual void OnEvent(EventArgs e) {  _testEvent.Raise(this, e); }}

 


注:相關(guān)教程知識閱讀請移步到c#教程頻道。
發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 昔阳县| 新和县| 安泽县| 古田县| 荔浦县| 柳林县| 望都县| 鄂尔多斯市| 巴青县| 乳山市| 永修县| 略阳县| 依安县| 仙桃市| 武清区| 梨树县| 浠水县| 唐河县| 河南省| 盈江县| 德保县| 兖州市| 云龙县| 高要市| 利辛县| 绍兴县| 准格尔旗| 浮山县| 宁陕县| 睢宁县| 平谷区| 丁青县| 嫩江县| 和田县| 盐山县| 新民市| 敦煌市| 洞口县| 扎鲁特旗| 清镇市| 金湖县|