【问题标题】:CallBack on garbagecollected Delegate垃圾收集代表的回调
【发布时间】:2009-11-30 12:47:14
【问题描述】:

我正在使用 USB 设备。此设备接收消息,我不知道何时或多久。驱动程序附带的 API 指定了一个 setreceiveCallBack 函数,该函数在设备接收到消息时提供回调。 但是在随机时间或间隔,我会收到有关垃圾收集委托异常的回调。我已经为我的问题寻找解决方案,但似乎没有一个解决方案适用于我的情况。 以下是我的代码的最大部分:

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()
        {     }
    }
}

【问题讨论】:

  • 为了便于阅读,我删除了一些代码并更正了错误(使用 _setCallBack 而不是 setCallBack)

标签: c# delegates callback dllimport


【解决方案1】:

在你的代码中,当你这样做时



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

在您调用“canusb_setReceiveCallBack”后,该委托将可用于垃圾回收,因为您的代码中没有引用该委托。

您可以避免将其存储在私有字段中。

例如:


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 管理。

【讨论】:

  • Kragen 指出了我错过的一些事情,即传入的是“call”而不是“setCallBack”。
  • oke 我将 setCallBack 调整为私有字段,并恢复了 _setCallBack 被传递而不是调用。因为该程序应该只使用 1 CallBack 我现在没有尝试你的 Safehandle 建议(而且我不确定我是否真的理解它)。现在去测试一下
  • 非常感谢。就像私有字段似乎工作一样简单。
【解决方案2】:

这是正确的/错字吗?:

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

您似乎创建了一个回调实例,但随后将其他内容传递给 canusb_setReceiveCallBack - 您的意思是改为传递 setCallBack 吗?

此外,在这一行中,您将 setCallBack 声明为局部变量,因此即使您传递了 setCallBack 而不是 call,您仍然传递了一个本地范围的变量,该变量可能会被垃圾回收(我注意到你做了GC.KeepAlive(setCallBack); 来明确防止这种情况)

【讨论】:

  • 我都试过了。我不确定是否必须通过 setCallBack 或是否可以通过函数本身。但这似乎没有任何区别,两者都给出了例外。我还在开始时声明了 setCallBack 以使其在我的应用程序的生命周期内保持活跃。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-01-18
  • 1970-01-01
  • 2011-01-21
  • 2016-12-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多