在C#中,事件是否保留对回调方法所在的整个类的引用? [英] In C#, Do events keep a reference to the entire class where the call back method is located?

查看:65
本文介绍了在C#中,事件是否保留对回调方法所在的整个类的引用?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个以ConcurrentDictionary作为私有成员的类. 此类还定义了一个委托/回调方法. 基类将此方法注册为外部事件的回调.这是唯一的一次.

我正在运行ANT内存探查器,并且从数百个ConcurrentDictionary属性实例中引用了数千个MyObj实例.这些事件的GC根是事件回调.

这似乎导致内存随着应用程序的运行而显着增加..大约5分钟后,该内存的大部分被回收了,但是我担心该应用程序可能会出现问题,因为它迅速膨胀并持续了很长时间,直到GC启动.

这是怎么回事,我该如何解决?

这是注册处理程序的基本调用的片段

protected abstract void DataReceivedEventHandler(DataChangedEvent evt);

public virtual void RegisterForChanges(ICollection<MemoryTable> tables)
{
    foreach (MemoryTable table in tables)
    {
        _subscribedTables.Add(table);
        table.RegisterEventListener(new DataChangedCallBack(this.DataReceivedEventHandler));

    }
}

以下是在上述基类的子类中实现的处理程序:

private ConcurrentDictionary<string, DataRecord> _cachedRecords;

protected override void DataReceivedEventHandler(DataChangedEvent evt)
{
    DataRecord record = evt.Record as DataRecord;
    string key = record.Key;
    if (string.IsNullOrEmpty(key)) { return; }

    if (_cachedRecords.ContainsKey(key))
    {
        _cachedRecords[key] = record;

        DateTime updateTime = record.UpdateTime;
        TimeSpan delta = updateTime - _lastNotifyTime;
        if (delta.TotalMilliseconds > _notificationFrequency)
        {
            PublishData(updateTime);
        }
    }
}

publishData方法发布棱镜事件

解决方案

是否有可能一遍又一遍地重新预订表?我看到了:

foreach (MemoryTable table in tables)
{
    _subscribedTables.Add(table);
    table.RegisterEventListener(new DataChangedCallBack(this.DataReceivedEventHandler));
}

并且我希望看到检查以确保没有重新预订表:

foreach (MemoryTable table in tables)
{
    if (!_subscribedTables.Contains(table)) {
        _subscribedTables.Add(table);
        table.RegisterEventListener(new DataChangedCallBack(this.DataReceivedEventHandler));
    }
}

考虑到问题开头的评论,我非常有信心这个问题(如果您可以称其为问题)就在这里:

if (_cachedRecords.ContainsKey(key))
{
    _cachedRecords[key] = record;

您在这里所说的是,如果记录的键已经存在于cachedRecords中,则用(可能是)新的行实例替换该值.这可能是因为某些后台处理导致该行的数据被更改,并且您需要将这些新值传播到UI.

我的猜测是MemoryTable类正在为这些更改创建DataRecord的新实例,并将该新实例沿事件链发送到我们在此处看到的处理程序.如果事件被触发了数千次,那么您最终将在内存中留下数千个事件.垃圾收集器通常可以很好地清理这些内容,但是您可能需要考虑就地更新,以避免在收集这些实例时发生大量的GC.

您应该要做的是尝试控制(甚至预测)GC何时运行.只要确保在GC收集完之后,多余的物体就消失了(换句话说,请确保它们没有泄漏),您会没事的.

i have a class that has a ConcurrentDictionary as a private member. This class also defines a delegate/call back method. The base class registers this method as the callback for an external event. this is one only ONCE.

I am running ANT memory profiler and i'm seeing 1000s of instances of MyObj referenced from hundreds of instances of the ConcurrentDictionary property. The GC root for these is the event callback.

This seems to be causing memory to rise significantly as the application runs..after maybe about 5 min or so, a good portion of that memory is reclaimed, but i'm worried that the app could potentially run into issue since it swells sto quickly and for so long before GC kicks in.

What's going on here and how do i resolve?

This is a snippet of the base calls that registers the handler

protected abstract void DataReceivedEventHandler(DataChangedEvent evt);

public virtual void RegisterForChanges(ICollection<MemoryTable> tables)
{
    foreach (MemoryTable table in tables)
    {
        _subscribedTables.Add(table);
        table.RegisterEventListener(new DataChangedCallBack(this.DataReceivedEventHandler));

    }
}

Here is the handler which is implemented in a subclass of the above mentioned baseclass:

private ConcurrentDictionary<string, DataRecord> _cachedRecords;

protected override void DataReceivedEventHandler(DataChangedEvent evt)
{
    DataRecord record = evt.Record as DataRecord;
    string key = record.Key;
    if (string.IsNullOrEmpty(key)) { return; }

    if (_cachedRecords.ContainsKey(key))
    {
        _cachedRecords[key] = record;

        DateTime updateTime = record.UpdateTime;
        TimeSpan delta = updateTime - _lastNotifyTime;
        if (delta.TotalMilliseconds > _notificationFrequency)
        {
            PublishData(updateTime);
        }
    }
}

The publishData method publishes prism events

解决方案

Is it possible that you are re-subscribing tables over and over again? I see this:

foreach (MemoryTable table in tables)
{
    _subscribedTables.Add(table);
    table.RegisterEventListener(new DataChangedCallBack(this.DataReceivedEventHandler));
}

and I'd expect to see a check to make sure that tables are not being re-subscribed:

foreach (MemoryTable table in tables)
{
    if (!_subscribedTables.Contains(table)) {
        _subscribedTables.Add(table);
        table.RegisterEventListener(new DataChangedCallBack(this.DataReceivedEventHandler));
    }
}

EDIT: Given the comments at the beginning of the question, I am fairly confident that the problem (if you can call it a problem) lies here:

if (_cachedRecords.ContainsKey(key))
{
    _cachedRecords[key] = record;

What you are saying here is that if the record's key already exists in cachedRecords, then replace the value with the (presumably) new row instance. This is probably because some background process caused the row's data to be changed and you need to propagate those new values to the UI.

My guess is that the MemoryTable class is creating a new instance of DataRecord for these changes, and sending that new instance up the event chain to the handler we see here. If the event is fired thousands of times, then of course you are going to end up with thousands of them in memory. The garbage collector is usually pretty good about cleaning these things up, but you might want to consider in-place updates to avoid the massive GC that will occur when these instances get collected.

What you should not do is try to control (or even predict) when the GC is going to run. Just make sure that after the GC collects, the excess objects are gone (in other words, make sure that they are not being leaked) and you will be alright.

这篇关于在C#中,事件是否保留对回调方法所在的整个类的引用?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆