自定义事件处理程序被调用两次

本文关键字:两次 调用 事件处理 程序 自定义 | 更新日期: 2023-09-27 18:13:39

我创建了一个事件处理程序,它只返回调用完成时从web服务接收到的对象列表。

现在我继续在调试模式下运行应用程序,发现第一次调用事件时它工作得很好,但在它完成事件后立即被第二次触发。我已经检查过了,并且绝对确定我没有在接收者类中多次调用事件。

这是我第一次在我的应用程序中创建自定义事件处理程序,所以我不完全确定实现是100%准确的。

你知道是什么原因造成的吗?我创建事件处理程序的方式准确吗?

这是DataHelper类

public class DataHelper
{
    public delegate void DataCalledEventHandler(object sender, List<DataItem> dateItemList);
    public event DataCalledEventHandler DataCalled;
    public DataHelper()
    {
    }
    public void CallData()
    {
        List<DataItem> dataItems = new List<DataItem>();
        //SOME CODE THAT RETURNS DATA
        DataCalled(this, dataItems);
    }
}

这是我订阅事件的地方:

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
   GetNewDataItems();
}
private void GetNewDataItems()
        {
                try
                {
                    DataHelper dataHelper = new DataHelper();
                    dataHelper.CallData();
                    dataHelper.DataCalled += new DataHelper.DataCalledEventHandler(dataHelper_DataCalled);
                }
                catch
                {
                   //Handle any errors
                }
            }
    }
    void dataHelper_DataCalled(object sender, List<DataItem> dataItemsList)
    {
        //Do something with results
        //NOTE: THIS IS WHERE THE EXCEPTION OCCURS WHEN EVENT IS FIRED FOR SECOND TIME
    }

自定义事件处理程序被调用两次

可能您添加了两次委托,是可能的吗?

在这种情况下,问题不在于谁调用委托,而在于谁将委托添加到事件中。

可能你做了像…

private Class1 instance1;
void callback(...)
{
}
void myfunction()
{
    this.instance1.DataCalled += this.callback;
    this.instance1.DataCalled += this.callback;
}

如果没有,尝试在订阅事件的地方添加一个断点,并查看是否调用了两次。

作为旁注,在调用事件时应该始终检查是否为空,如果没有订阅者,则可以获得NullReferenceException。我还建议您使用变量来存储事件委托,以避免多线程失败的风险。

public void CallData()
{
    List<DataItem> dataItems = new List<DataItem>();
    var handler = this.DataCalled;
    if (handler != null)
        handler(this, dataItems);
}
编辑:因为现在我看到代码,很明显,每次调用GetNewDataItems方法时,每次都订阅事件。以这样的方式进行订阅,例如,在构造函数中,或者将变量存储在某个地方,或者在完成时注销事件。

这段代码还包含一个可能的内存泄漏:每次添加委托时,至少要保持包含事件的实例和包含订阅方法的实例都是活动的,直到它们都未被引用。

你可以试着这样做…

void dataHelper_DataCalled(object sender, List<DataItem> dataItemsList)
{
    // Deregister the event...
    (sender as Class1).DataCalled -= dataHelper_DataCalled; 
    //Do something with results
}

通过这种方式,您必须确保如果在事件注册期间没有异常,则事件将被触发,否则您将再次发生内存泄漏。

也许你只需要一个委托而不是事件。当然,当你想要释放委托时,你应该将委托字段设置为null。

// in data helper class
private DataHelper.DataCalledEventHandler myFunctor;
public void CallData(DataHelper.DataCalledEventHandler functor)
{
    this.myFunctor = functor;
    //SOME CODE THAT RETURNS DATA
}
// when the call completes, asynchronously...
private void WhenTheCallCompletes()
{
    var functor = this.myFunctor;
    if (functor != null)
    {
        this.myFunctor = null;
        List<DataItem> dataItems = new List<DataItem>();
        functor(this, dataItems);
    }
}
    
// in your function
...    dataHelper.CallData(this.dataHelper_DataCalled);    ...

代码中的以下行应该翻转。这就是

这些行

dataHelper.CallData();
dataHelper.DataCalled += new DataHelper.DataCalledEventHandler(dataHelper_DataCalled);
应:

dataHelper.DataCalled += new DataHelper.DataCalledEventHandler(dataHelper_DataCalled);
dataHelper.CallData();

因为您首先需要附加事件处理程序,然后调用对象上可以引发事件的其他方法