【问题标题】:NullReferenceException when attempting to fire an event尝试触发事件时出现 NullReferenceException
【发布时间】:2012-11-04 17:40:48
【问题描述】:

我正在学习创建事件和创建多线程应用程序。

方法Thread 被另一个类调用,该类使用搜索条件填充参数。创建BackgroundWorker,执行搜索并将结果返回给worker_RunWorkerCompleted

worker_RunWorkerCompleted 内,我想将结果发送回订阅Fireendofsearch 事件的UI。

我无法理解为什么下面的代码会引发以下错误

对象引用未设置为对象的实例。

当我触发事件Fireendofsearch

public class BackgroundSearch
{
    public event SearchResultCompleteThreaded Fireendofsearch;
    public EventArgs a = null;
    public delegate void SearchResultCompleteThreaded(object seachresults, EventArgs a);
    internal void Thread(string folder, string parms)
    {
        var Argument = new List<object> { folder, parms };
        var worker = new BackgroundWorker {WorkerReportsProgress = false, WorkerSupportsCancellation = false};
        worker.DoWork += worker_DoWork;          
        worker.RunWorkerCompleted += worker_RunWorkerCompleted;
        worker.RunWorkerAsync(Argument);
    }
    void worker_DoWork(object sender, DoWorkEventArgs e)
    {
        var passedAugue = e.Argument as List<object>;
        var returnResult = new List<string[]>();
        if (passedAugue != null)
        {
            var result = Directory.GetFiles(passedAugue[0].ToString(), passedAugue[1].ToString(), SearchOption.AllDirectories);
            foreach (string s in result)
            {
                var t = new string[4];
                t[0] = s;
                t[1] = File.GetCreationTime(s).ToString();
                t[2] = File.GetLastAccessTime(s).ToString();
                t[3] = File.GetLastWriteTime(s).ToString();
                returnResult.Add(t);
            }
        }
        if (returnResult.Count != 0)
        {
            e.Result = returnResult;
        }            
    }
    void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        if (e.Result != null)
        {
            Fireendofsearch(e.Result, a);
        }
    }
}

【问题讨论】:

  • 更新:问题与订阅有关。我在 UI 线程中订阅了该事件,但该事件是由另一个类的“代理”引发的。 IE。 UI 调用了 A 类中的一个方法,A 类调用了 B 类中的另一个方法。B 类引发了事件,而不是 A 类。移动事物我得到了我想要的结果,但我忍不住想我我错过了一些东西。为什么我不能从通过 A 类发起的 UI 订阅 B 类中的事件?

标签: c# multithreading events


【解决方案1】:

在有人订阅之前,Firendofsearch 将为空,请将您的工作完成事件处理程序更改为此来修复它。

void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    if (e.Result != null)
    {
        var friendOfSearch = Fireendofsearch;
        if(friendOfSearch != null)
           friendOfSearch (e.Result, a);
    }
}

我将它复制到变量的原因是,如果另一个线程中的某人是最后一个在 null 检查和引发事件之间取消订阅的人,你仍然会得到 null 引用异常,通过首先处理另一个变量它解决了那个问题。


但是,如果我在哪里编写它,我会进行一些其他更改,您出于某种原因重新调整 null EventArgs 并将结果作为传统事件模式中的“发送者”传递回。我会把你的代码改成这个

public event EventHandler<FriendOfSearchArgs> FirendsOfSearch;

void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    if (e.Result != null)
    {
        RaiseFriendOfSearch(e.Result);
    }
}

protected virtual void RaiseFriendOfSearch(object result)
{
    var friendOfSearch = FirendsOfSearch;
    if(friendOfSearch != null)
        friendOfSearch(this, new FriendOfSearchArgs(result));
}


public class FriendOfSearchArgs : EventArgs
{
   public FriendOfSearchArgs(object result)
   {
       Result = result;
   }

   public object Result {get; private set;}
}

这都是在 SO 文本框中写的,所以可能会有一两个错误。

【讨论】:

    【解决方案2】:

    在尝试调用委托之前,您应该检查 null。而且你必须先把它拉到一个单独的变量中以避免线程问题。

    var ev = Fireendofsearch;
    if ( ev != null ) ev( ... );
    

    我还发现在这种情况下有一个扩展方法很有用:

    public static void Raise ( this EventHandler h, object sender )
    {
        if ( h != null) h( sender, EventArgs.Empty );
    }
    

    然后:

    MyEvent.Raise ( this );
    

    【讨论】:

      【解决方案3】:

      在你的公共事件“后面”,有一个SearchResultCompleteThreaded 类型的隐式私有变量。 SearchResultCompleteThreaded 类型是委托类型。

      在 .NET 中,所有委托都是“多播”委托。这意味着他们有一个调用 listSearchResultCompleteThreaded 上的 GetInvocationList() 方法派生自 System.Delegate.GetInvocationList()

      现在,在 .NET 中,调用列表保证由 一个 或多个项目(不是零个或多个)组成。任何委托类型都是不可变类型。但是,如果您尝试通过“减去”现有实例的调用列表中的所有成员来创建新实例,如var newDel = oldDel - oldDel;reuseDel -= reuseDel;,那么与其获取具有零长度调用列表的新实例,你得到一个空引用!

      这样做的好处是您不必担心“空”委托实例(本来可以允许)和空引用之间的细微差别。不好的地方是你上面的问题。

      【讨论】:

        【解决方案4】:

        由于某种原因(很可能是优化),一个事件仅在第一个处理程序方法订阅它时才被实例化。

        您必须在代码中检查这一点。

        这是我通常声明事件的方式:

        public event SearchResultCompleteThreaded Fireendofsearch;
        
        private void RaiseFireEndOfSearchEvent(EventArgs e)
        {
            if (Fireendofsearch != null)
            {
               Fireendofsearch(this, e);
            }
        }
        

        每当我需要引发事件时,我都会调用辅助方法。

        【讨论】:

        • 为什么需要线程安全?它是一个事件的简单包装器。喜欢if (event != null) event()。如果它为空,则由于锁定,它将保持为空,如果它有一个处理程序,因此不为空,则实例化事件,即使它没有更多的订阅者。除非您将事件设置为 null,否则这根本不是问题。
        【解决方案5】:

        设置:

        public event SearchResultCompleteThreaded Fireendofsearch =  delegate { };
        

        可能需要初始化吗?

        【讨论】:

          猜你喜欢
          • 2018-03-25
          • 1970-01-01
          • 2020-04-15
          • 2021-08-25
          • 2019-11-13
          • 1970-01-01
          • 1970-01-01
          • 2015-07-09
          相关资源
          最近更新 更多