尝试触发事件时出现了NullReferenceException异常

3
我正在学习如何创建事件和多线程应用。方法Thread由另一个类调用,该类使用搜索条件填充参数。创建BackgroundWorker,执行搜索并将结果返回到worker_RunWorkerCompleted中。在worker_RunWorkerCompleted内,我想将结果发送回我的UI,该UI正在订阅Fireendofsearch事件。
当我触发事件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);
        }
    }
}

1
更新:问题与订阅有关。我在UI线程中订阅了事件,但是此事件是通过另一个类的“代理”引发的。即UI调用类A中的方法,类A然后调用类B中的另一个方法。类B引发事件,而不是类A。移动一些东西,我得到了我想要的结果,但我无法帮助思考我是否遗漏了什么。为什么我不能从通过类A引发的UI中订阅类B中的事件? - Damo
5个回答

2

如果没有人订阅Firendofsearch,它将为空,将你的工作完成事件处理程序更改为以下内容即可解决此问题。

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

我将其拷贝到一个变量中的原因是,如果在空检查和事件引发之间的时间里,另一个线程中的某个人是最后一个取消订阅的人,则仍将引发null引用异常。通过先复制到另一个变量中,可以解决这个问题。


但是,如果我编写它,我会进行其他一些更改,出于某种原因,你返回了一个空的EventArgs并将结果作为“Sender”传回传统的事件模式。 我会将您的代码更改为以下内容

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文本框中写的,所以可能会有一两个错误。


0
由于某种原因(很可能是优化),只有在第一个处理程序方法订阅事件时,事件才会被实例化。
您必须在代码中检查这一点。
以下是我通常声明事件的方式:
public event SearchResultCompleteThreaded Fireendofsearch;

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

每当我需要触发事件时,我只需调用辅助方法即可。

为什么需要线程安全?它只是一个简单的事件包装器,例如 if (event != null) event()。如果它为空,由于锁定,它将保持为空,如果它有一个处理程序,那么事件将被实例化,即使它没有更多的订阅者,也不会为空。除非您将事件设置为null,否则这根本不是问题。 - Aurelia

0
在尝试调用委托之前,您应该检查是否为 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 );

0

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

在.NET中,所有委托都是“多路广播”委托。这意味着它们具有调用列表(你在SearchResultCompleteThreaded上的GetInvocationList()方法源自System.Delegate.GetInvocationList())。

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

这个好处在于你不必担心“空”委托实例(本来是可以允许的)和空引用之间的微妙差别。坏处就是你上面遇到的问题。


-1

设置:

public event SearchResultCompleteThreaded Fireendofsearch =  delegate { };

可能需要初始化吗?


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接