关于c#:在线程之间的Invoke()上检测到CallbackOnCollectedDelegate

CallbackOnCollectedDelegate was detected on Invoke() between threads

我已经阅读了类似问题的解决方案,但是在使它们适合我的情况下遇到了麻烦。我在Visual Studio 2010中运行的C#.NET 4.0项目上遇到以下错误:

CallbackOnCollectedDelegate was detected Message: A callback was made
on a garbage collected delegate of type
'VLCMTest!VLCMTest.Data+VCECLB_GrabFrame_CallbackEx::Invoke'. This may
cause application crashes, corruption and data loss. When passing
delegates to unmanaged code, they must be kept alive by the managed
application until it is guaranteed that they will never be called.

这是我的情况:我有一个后台线程,在收集一帧数据时会得到通知。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
protected AutoResetEvent frameGrabbed;
public event EventHandler<DataFrameInfo> FrameGrabbedEvent;

    private void DataCollectionThread()
    {
        while (true)
        {
            frameGrabbed.WaitOne();

            lock (locker)
            {
                FrameGrabbedEvent(this, new DataFrameInfo(lastFrame.BufferIndex, lastFrame.FrameNumber, lastFrame.FrameTimestamp));
            }
        }
    }

DataFrameInfo类存储有关帧的一些信息(到帧缓冲区,帧号和时间戳的索引)。我创建了此类的一个实例,并将其传递给主线程,以便可以显示数据。主线程中的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
    // Delegate for the Invoke call,
    // make it static to prevent a problem with garbage collection
    delegate void GetFrameDelegate(DataFrameInfo frameInfo);
    private static GetFrameDelegate d;

    /// <summary>
    /// Did we just receive a frame?
    /// </summary>
    /// <param name="source"></param>
    /// <param name="args"></param>
    void frameGrabbed(object source, DataFrameInfo args)
    {
        if (this.InvokeRequired)
        {
            // It's on a different thread, so use Invoke.
            d = new GetFrameDelegate(GetFrame);
            this.Invoke(d, new object[] { args });
            return;
        }

        // Get the Frame
        GetFrame(args);
    }

    private void GetFrame(DataFrameInfo frameInfo)
    {
        // Call Display Frame
        Debug.WriteLine("Frame: The bufferIndex is" + frameInfo.BufferIndex);
        Debug.WriteLine("Frame: The number is" + frameInfo.FrameNumber);
        Debug.WriteLine("Frame: The timestamp is" + frameInfo.FrameTimestamp / 1000);
    }

FrameGrabbedEvent连接到frameGrabbed()函数。由于从另一个线程调用frameGrabbed(),因此应要求调用Invoke。然后,现在,我只是想在显示数据之前转储帧的详细信息。

足够有趣的是,该程序将在一段时间内正常运行。一旦在桌面上移动程序的主窗口,该错误几乎立即出现。我必须充分改变时间,以使对象在使用前得到垃圾回收。似乎最建议的解决方案是使委托静态化,但这对我不起作用。

更新

像我这样的声音错过了一些相关的代码。以下是导致frameGrabbed事件的代码。本质上,这是一个中断处理程序,由我正在使用的DLL调用。

我声明以下内容:

1
2
// Function pointer used by StartGrabEx
public delegate void GrabFrame_CallbackEx(IntPtr userData, ref FrameInfoEx frameInfo);

然后我使用以下命令开始数据收集:

1
2
3
4
5
6
7
8
    public void Start()
    {

        // Start grabbing frames
        isGrabRunning = true;
        GrabFrame_CallbackEx callback = new GrabFrame_CallbackEx(GrabCallback);
        StartGrabEx(callback);
    }

然后GrabCallback函数如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    FrameInfo lastFrame;

    private void GrabCallback(IntPtr userData, ref FrameInfoEx frameInfo)
    {
        // Are we grabbing frames?
        if (!isGrabRunning)
        {
            return;
        }

        lock (locker)
        {
            lastFrame = new DataFrameInfo(bufferIndex, frameInfo.number, frameInfo.timestamp);
        }

        // We've captured a frame, notify the DataCollectionThread
        frameGrabbed.Set();
    }

查看此更新,也许问题出在lastFrame上,尽管我认为我更改了代码以在GrabCallback中更新lastFrame(而不是使用new),但仍然失败。

更新#2

也许我还应该提到DataCollectionThread是这样声明的:

1
2
3
4
DataCollectionThread = new Thread(DataCollectionThread );
DataCollectionThread .Name ="DataCollectionThread";
DataCollectionThread .IsBackground = true;
DataCollectionThread .Start();


正如汉斯·帕桑(Hans Passant)所说,问题出在Start()方法中的回调变量上。我将其更改为以下内容:

1
2
3
4
5
6
7
8
9
10
private GrabFrame_CallbackEx callback;

public void Start()
{

    // Start grabbing frames
    isGrabRunning = true;
    callback = new GrabFrame_CallbackEx(GrabCallback);
    StartGrabEx(callback);
}

现在一切正常!