欧美大屁股bbbbxxxx,狼人大香伊蕉国产www亚洲,男ji大巴进入女人的视频小说,男人把ji大巴放进女人免费视频,免费情侣作爱视频

歡迎來到入門教程網(wǎng)!

C#教程

當(dāng)前位置:主頁(yè) > 軟件編程 > C#教程 >

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

來源:本站原創(chuàng)|時(shí)間:2020-01-10|欄目:C#教程|點(diǎn)擊: 次

內(nèi)存泄漏是指:當(dāng)一塊內(nèi)存被分配后,被丟棄,沒有任何實(shí)例指針指向這塊內(nèi)存, 并且這塊內(nèi)存不會(huì)被GC視為垃圾進(jìn)行回收。這塊內(nèi)存會(huì)一直存在,直到程序退出。C#是托管型代碼,其內(nèi)存的分配和釋放都是由CLR負(fù)責(zé),當(dāng)一塊內(nèi)存沒有任何實(shí)例引用時(shí),GC會(huì)負(fù)責(zé)將其回收。既然沒有任何實(shí)例引用的內(nèi)存會(huì)被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;
  //強(qiáng)制垃圾回收
  GC.Collect(GC.MaxGeneration);
  Console.WriteLine("GC.Collect");
  //測(cè)試是否回收成功
  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ā)事件后,會(huì)在屏幕上看到 TestAction Run。當(dāng)memory =null 后,memory原來指向的內(nèi)存就沒有任何實(shí)例再引用該塊內(nèi)存了,這樣的內(nèi)存就是待回收的內(nèi)存。GC.Collect(GC.MaxGeneration)語(yǔ)句會(huì)強(qiáng)制執(zhí)行一次垃圾回收,再次引發(fā)事件,發(fā)現(xiàn)屏幕上還是會(huì)顯示TestAction Run。該內(nèi)存沒有被GC回收,這就是內(nèi)純泄漏。這是由TestEvent+=memory.Run語(yǔ)句引起的,當(dāng)GC.Collect執(zhí)行的時(shí)候,當(dāng)他看到該塊內(nèi)存還有TestEvent引用,就不會(huì)進(jìn)行回收。但是該內(nèi)存已經(jīng)是“無(wú)法到達(dá)”的了,即無(wú)法調(diào)用該塊內(nèi)存,只有在引發(fā)事件的時(shí)候,才能執(zhí)行該內(nèi)存的Run方法。這顯然不是我想要的效果,當(dāng)memory = null執(zhí)行時(shí),我希望該內(nèi)存在GC執(zhí)行時(shí)被回收,并且當(dāng)TestEvent被引發(fā)時(shí),Run方法不會(huì)執(zhí)行,因?yàn)槲乙呀?jīng)把該內(nèi)存“解放”了。

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

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

IL_0000: nop
IL_0001: newobj  instance void EventLeakMemory.Program/TestAction::.ctor()
IL_0006: stloc.0
IL_0007: ldloc.0
IL_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行,聲明了一個(gè)System.Action型的委托,參數(shù)為TestAction.Run方法,第七行,執(zhí)行了Program.add_TestEvent方法,參數(shù)是上面聲明的委托。也就是說+=操作符相當(dāng)于執(zhí)行了Add_TestEvent(new Action(memory.Run)),就是這個(gè)new Action包含了對(duì)memory指向的內(nèi)存的引用。而這個(gè)引用在CLR看來是可達(dá)的,可以通過引發(fā)事件來調(diào)用該內(nèi)存。

解決辦法

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

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。這個(gè)gcHandle就是obj的一個(gè)弱引用。這個(gè)類的詳細(xì)介紹見 GCHandle 。比較關(guān)鍵的是GCHandle.Alloc方法的第二個(gè)參數(shù),該參數(shù)接受一個(gè)枚舉類型。我使用的是GCHandleType.Weak,表明該引用是個(gè)弱引用。利用這個(gè)方法,就可以封裝一個(gè)自己的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;
  }
 }
}

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

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

上一篇:C#利用GDI+給圖片添加文字(文字自適應(yīng)矩形區(qū)域)

欄    目:C#教程

下一篇:C#客戶端程序調(diào)用外部程序的3種實(shí)現(xiàn)方法

本文標(biāo)題:C#中event內(nèi)存泄漏總結(jié)

本文地址:http://mengdiqiu.com.cn/a1/C_jiaocheng/5217.html

網(wǎng)頁(yè)制作CMS教程網(wǎng)絡(luò)編程軟件編程腳本語(yǔ)言數(shù)據(jù)庫(kù)服務(wù)器

如果侵犯了您的權(quán)利,請(qǐng)與我們聯(lián)系,我們將在24小時(shí)內(nèi)進(jìn)行處理、任何非本站因素導(dǎo)致的法律后果,本站均不負(fù)任何責(zé)任。

聯(lián)系QQ:835971066 | 郵箱:835971066#qq.com(#換成@)

Copyright © 2002-2020 腳本教程網(wǎng) 版權(quán)所有