CallBack на мусорном собрании делегата - PullRequest
1 голос
/ 30 ноября 2009

Я работаю с 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()
        {     }
    }
}

Ответы [ 2 ]

2 голосов
/ 30 ноября 2009

В вашем коде, когда вы делаете



                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 будет управлять временем жизни делегата 'callback' и будет управляться SafeHandle.

2 голосов
/ 30 ноября 2009

Это правильно / опечатка?:

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

Вы, кажется, создаете экземпляр callBack, но затем передаете что-то еще canusb_setReceiveCallBack - вы хотели передать setCallBack вместо этого?

Кроме того, в этой строке вы объявляете setCallBack как локальную переменную, и поэтому, даже если вы передаете setCallBack вместо call, вы все равно передаете переменную локальной области видимости, которая, вероятно, будет собираться мусором (I заметил, что вы делаете GC.KeepAlive(setCallBack); , чтобы явно предотвратить это)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...