2009-11-30 69 views
1

我正在使用USB設備。此設備接收消息,我不知道何時或多久。驅動程序附帶的API指定了一個setreceiveCallBack函數,該函數在設備收到消息時提供回調。 但隨機的時間或間隔,我收到garbagecollected委託exeption回調。我已經爲我的問題尋找解決方案,但沒有一個解決方案似乎適用於我的情況。 以下是我的代碼的最重要的部分:回收垃圾收集代表

using System; 
using System.Collections.Generic; 
using System.ComponentModel; 
using System.Data; 
using System.Drawing; 
using System.IO; 
using System.Linq; 
using System.Text; 
using System.Runtime.InteropServices; 
using System.Windows.Forms; 

namespace CallBacktesting 
{ 
    public unsafe delegate void callBack(Form1.CANMsg *pmsg); 

    public partial class Form1 : Form 
    { 
     uint handle; 
     static WriteLog log = new WriteLog(); 
     Boolean getCan = false; 
     static int frameCount = 0; 
     static CANMsg newmsg = new CANMsg(); 
     callBack _setCallBack; 
     List<string> write = new List<string>(); 

     public Form1() 
     { 
      InitializeComponent(); 
     } 


     private void buttonOpen_Click(object sender, EventArgs e) 
     { 
       // Open connection 
     } 

     private void buttonClose_Click(object sender, EventArgs e) 
     { 
       // Close connection 
     } 

     private void buttonCallBack_Click(object sender, EventArgs e) 
     { 
      if (!getCan) 
      { 
       int rv; 
       unsafe 
       { 
        callBack _setCallBack = new callBack(call); 
        rv = canusb_setReceiveCallBack(handle, _setCallBack); 
       } 
       label1.Text = rv.ToString(); 
      } 
      else 
      { 
       _setCallBack = null; 
       int rv = canusb_setReceiveCallBack(handle, _setCallBack); 
       GC.KeepAlive(_setCallBack); 
       label1.Text = rv.ToString(); 
      } 
     } 

     public unsafe void call(CANMsg *pmsg) 
     { 
      newmsg = *pmsg; 
      update(); 
     } 

     private void buttonExit_Click(object sender, EventArgs e) 
     { 
      GC.KeepAlive(_setCallBack); 
      Application.Exit(); 
     } 

     [DllImport("canusbdrv.dll", EntryPoint = "canusb_setReceiveCallBack")] 
     public static extern int canusb_setReceiveCallBack(uint handle, callBack callBack); 

     unsafe private void timer_Tick(object sender, EventArgs e) 
     { 
       // update the form with received messages 
     } 

     public void update() 
     { 
      CANMsg msgrec = newmsg; 
      // Build str from messages with all data 
      write.Add(str); 
      log.logWrite(str); 
      frameCount++; 
     } 
    } 

    public class WriteLog 
    { 

     private void OpenFile() 
     {  } 

     public void logWrite(string log) 
     {  } 

     public void logAdd(string log) 
     {  } 

     private void logClose() 
     {  } 
    } 
} 
+0

我刪除了一些代碼的可讀性和糾正錯誤(用_setCallBack代替setCallBack) – 2009-12-01 09:17:34

回答

2

在你的代碼,當你做



       callBack setCallBack = new callBack(call); 
       rv = canusb_setReceiveCallBack(handle, call); 

委託將可用於垃圾收集調用「canusb_setReceiveCallBack」,因爲在代碼中任何地方引用的委託後。

你可以避免這個存儲在私人領域。

E.x:


Class Form1 
{ 

callBack _setCallBack; 

private void buttonCallBack_Click(object sender, EventArgs e) 
{ 


       _setCallBack = new callBack(call); 
       rv = canusb_setReceiveCallBack(handle, _setCallBack); 

} 

} 

但是,這可能有一些問題,因爲每個按鈕的點擊將創建一個新的回調。如果以前的回調需要被引用,這可能會有問題。

我認爲你應該做的是重構代碼以使用SafeHandle來存儲canusb_Open返回的句柄。

我會設計這樣的類。


class CanUsbSafeHandle : SafeHandle 
{ 
    private EventHandler _receiveCallBack; 
    private readonly object _receiveCallBackLock = new object(); 

    public event EventHandler ReceiveCallBack 
    { 
     add 
     { 
      lock (_receiveCallBackLock) 
      { 
       bool hasListeners = (_receiveCallBack != null); 
       _receiveCallBack += value; 
       //call canusb_setReceiveCallBack only when 1 or more listeners were added 
       //and there were previously no listeners 
       if (!hasListeners && (_receiveCallBack != null)) 
       { 
        canusb_setReceiveCallBack(this, setCallBack); 
       } 
      } 
     } 
     remove 
     { 
      lock (_receiveCallBackLock) 
      { 
       bool hasListeners = (_receiveCallBack != null); 
       _receiveCallBack -= value; 
       //call canusb_setReceiveCallBack only when there are no more listeners. 
       if(hasListeners && (_receiveCallBack == null)) 
       { 
        canusb_setReceiveCallBack(this, null); 
       } 
      } 
     } 
    } 

    public CanUsbSafeHandle() 
     : base(IntPtr.Zero, true) 
    { 
    } 

    public override bool IsInvalid 
    { 
     get { return handle == IntPtr.Zero; } 
    } 

    protected override bool ReleaseHandle() 
    { 
     return canusb_Close(handle); 
    } 

    protected override void Dispose(bool disposing) 
    { 
     if (disposing) 
     { 
      lock (_receiveCallBackLock) 
      { 
       _receiveCallBack = null; 
      } 
     } 
     base.Dispose(disposing); 
    } 
} 

這樣,SafeHandle將管理'接收回調'委託的生命期將由SafeHandle管理。

+0

Kragen指出了我錯過的一件事,那就是'call'正在被傳入,而不是'setCallBack'。 – 2009-11-30 14:04:14

+0

oke我將setCallBack調整爲專用字段,並恢復了傳遞的_setCallBack而不是調用。 因爲程序應該只使用1回調我現在不嘗試你的安全帶建議(我不確定我真的明白它)。 現在要測試它 – 2009-11-30 14:08:51

+0

非常感謝。就像私人領域一樣簡單。 – 2009-11-30 14:44:43

2

這是正確的/一個錯字?:

callBack setCallBack = new callBack(call); 
rv = canusb_setReceiveCallBack(handle, call); 

您出現創建的回調的一個實例,但後來通過別的東西canusb_setReceiveCallBack - 你的意思是通過setCallBack而不是?

而且,在這條線,你都宣稱setCallBack是一個局部變量,所以即使你通過setCallBack而不是call,你還在傳遞一個局部範圍,這將很可能被垃圾收集變量(我注意到,你做GC.KeepAlive(setCallBack); 明確阻止這種)

+0

我試了兩種。我不確定是否必須傳遞setCallBack或者可以傳遞函數本身。但它似乎沒有任何區別,都表明了例外。 我還在開始時聲明瞭setCallBack,使其在我的應用程序的生命週期中處於活動狀態。 – 2009-11-30 13:52:37