Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/haskell/9.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# .NET事件在被调用方超出范围后回调:安全做法?_C#_.net_Garbage Collection - Fatal编程技术网

C# .NET事件在被调用方超出范围后回调:安全做法?

C# .NET事件在被调用方超出范围后回调:安全做法?,c#,.net,garbage-collection,C#,.net,Garbage Collection,(如果之前出现过此问题,我表示歉意,因为我已经搜索了,但没有找到我的搜索词的任何内容) 鉴于以下情况: void Method1 { Foo _foo = new Foo(); _foo.DataReady += ProcessData(); _foo.StartProcessingData(); } void ProcessData() { //do something } StartProcessingData()是一个长时间运行的方法,它最终(异步)引发Data

(如果之前出现过此问题,我表示歉意,因为我已经搜索了,但没有找到我的搜索词的任何内容)

鉴于以下情况:

void Method1 {
    Foo _foo = new Foo();
    _foo.DataReady += ProcessData();
    _foo.StartProcessingData();
}

void ProcessData() { //do something }
StartProcessingData()
是一个长时间运行的方法,它最终(异步)引发
DataReady
事件。(比如说它打了个服务电话之类的)

现在,
\u foo
过去是一个类级变量,事件过去连接到构造函数中

然而,内存分析强调了这将如何将
\u foo
及其所有依赖项永久保留在内存中,因此我对上述内容进行了更改

我的问题是:有没有过GC会毁掉一切的情况
Method1
很快就完成了(当然是在事件触发之前),这意味着
\u foo
停止了。但是,这是否意味着(因为_foo保留其事件的引用)
ProcessData()
永远不会触发?或者,事件的存在是否足以在方法结束后保持\u foo的活动状态,足以确保ProcessData触发?还是没有定论

[在测试中,它工作得很好-
ProcessData
总是被调用。即使使
StartProcessingData
花费很长时间,并且中途强制GC收集(使用RedGate的内存分析器)也没有将其删除。但我想确定一下!]

为了澄清:
StartProcessingData()
立即返回。
Foo
对象类似于:

class Foo
{
SomeSerice _service;
event EventHandler<EventArgs> DataReady;

Foo()
{
_service = new SomeService();
_service.ServiceCallCompleted += _service_ServiceCallCompleted;
}

void StartProcessingData()
{
_service.ServiceCallAsync();
}

void _service_ServiceCallCompleted
{
DataReady(null,e);
}

让我们假设
StartProcessingData()
是完全同步的(即不涉及线程)。它将在事件激发后才会返回,并且将从
\u foo.StartProcessingData()
中调用
ProcessData()
。如果要验证这一点,请在
ProcessData()
中放置一个断点,然后查看调用堆栈

因此,也就是说,
\u foo
在触发事件并调用处理程序时不会超出范围,因为
Method1()
尚未返回

现在,如果涉及线程,这意味着在另一个线程中执行的代码必须包含对
\u foo
的引用;否则,事件将不可能被触发。因此,
\u foo
仍然不是垃圾收集的候选对象。因此,无论哪种情况,您都不必担心垃圾被收集

(编辑)


挂接
\u service
ServiceCallCompleted
事件现在意味着
\u service
包含对
\u foo
的引用,防止垃圾收集。

对象
Foo
可以从对象
Bar
订阅事件,然后放弃对
Bar
的所有引用,在大多数情况下不会影响
Bar
触发事件的能力,因为
Bar
可能会由于某个线程或外部对象而触发其事件,这只有在该线程或外部对象引用它时才会发生。有两个原因可能很重要
Foo
保留对
Bar
的引用;这两种情况都不可能适用于你的情况

  • 如果'Foo'没有保留对'Bar'的任何引用,那么如果它以后发现自己不需要'Bar'的服务,它可能无法让'Bar'知道。它可以尝试使用稍后某个事件回调的`Sender`参数取消订阅,但这至少有两个问题:(1)有时一个对象会代表另一个对象引发事件,在这种情况下`Sender`属性可能无法识别持有事件订阅的对象;(2) 事件可能永远不会引发,但对订阅服务器的引用可能会阻止订阅服务器符合垃圾收集的条件,直到发布服务器被激活。
  • 系统中任何地方对“Bar”的其他引用都可能是“WeakReference”类型,因此如果“Foo”没有保留对“Bar”的强引用,系统可能会使所有这些弱引用无效,从而导致“Bar”消失。
    就我个人而言,我认为让对象在放弃订阅之前取消所有订阅是个好主意。人们可能会认为,在放弃订阅服务器时,事件发布服务器将超出范围,但如果事件保持连接,则任何使发布服务器保持活动状态的东西都将无用地使被放弃的订阅服务器保持活动状态。如果这些被抛弃的订阅者中的任何一个同时也是事件发布者,那么他们被抛弃的订阅者可能会被无用地保留下来,等等。

    StartProcessingData是静态的,这是一种打字错误吗?或者你的意思是把_foo.StartProcessingData()放进去是的,这是个打字错误。现在修复:谢谢:)我不确定我是否完全理解你。我假设
    StartProcessingData
    将实际调用处理程序。如果是这种情况,
    Method1
    将不会像现在的代码那样在调用完成之前完成。抱歉,我没有说明StartProcessingData是否会异步引发事件。我将编辑原始文件。一旦
    StartProcessingData()
    完成,在引发事件之前,当
    \u foo
    不存在时,在这段时间内会发生什么?我原本以为事件不会被引发,因为它已经不存在了,但事实似乎并非如此。如果
    StartProcessingData()
    是同步的,那么您误解了执行顺序
    StartProcessingData()
    在触发事件之前不会返回。如果涉及线程,则另一个线程持有对
    \u foo
    的引用,并防止其被垃圾收集。将代码发布到
    Foo
    对象可能会有所帮助。StartProcessingData将立即返回。我将为Foo对象发布一些psedoo代码。听你们两个说的:这就是我困惑的原因_福保留了推荐信
    class Program
            {
                static void Main(string[] args)
                {
                    Class1 _class1 = new Class1();
                    Console.WriteLine("Disposing of Class 1");
                    _class1 = null;
    
                    GC.Collect();
    
                    System.Threading.Thread.Sleep(15000);
                    Console.Read();
    
                }
    
            }
    
            internal class Class1
            {
                internal Class1()
                {
                    Foo _foo = new Foo();
                    _foo.DataReady += new EventHandler<EventArgs>(_foo_DataReady);
    
                    _foo.StartProcessingData();
                }
    
                void _foo_DataReady(object sender, EventArgs e)
                {
                    Console.WriteLine("Class 1 Processing Data");
                }
            }
    
            class Foo
            {
                internal event EventHandler<EventArgs> DataReady = delegate { };
    
                internal void StartProcessingData()
                {
                    System.Threading.Timer _timer = new System.Threading.Timer(OnTimer);
    
                    Console.WriteLine("Firing event in 10 secs");
                    _timer.Change(10000, System.Threading.Timeout.Infinite);
                }
    
                private void OnTimer(object state)
                {
                    DataReady(this, null);
                }
        }
    
    Firing event in 10 secs
    Disposing of Class 1
    Class 1 Processing Data