2013-07-31 40 views
3

我有一个带有按钮,标签和进度条的窗体,这样当我单击该按钮时,它将创建一个类b的实例来运行进程。一旦这个过程完成,它会调用一个EventHandler在主窗体的标签中显示“done”!为什么一个事件是空的? (对象引用未设置为对象的实例)

我创建了一个委托(SetStatus)的事件(SetStatusEvent)来做到这一点。它似乎很好,当我调用事件处理(usbforProcessExited)外的这个事件,但是当我把它从usbforProcessExited它给出了一个错误 -

object reference not set to an instance of an object 

主要形式

public partial class main : Form 
{ 
    b rsSet = new b(); 

    public main() 
    { 
     InitializeComponent(); 
     rsSet.SetStatusEvent += new RemoteS.SetStatus(updateStatus); 
    } 

    private void button1_Click(object sender, EventArgs e) 
    { 
     rsSet.FormatUSB(); 
    } 

    private delegate void UpdateStatus(int i, string str, Color clr); 

    private void SetStatus(int i, string str, Color clr) 
    { 
     this.progressBar1.Value = i; 
     this.lbl_status.ForeColor = clr; 
     this.lbl_status.Text = str; 
    } 

    private void updateStatus(int i, String msg, Color color) 
    { 
     object[] p = GetInokerPara(i, msg, color); 
     BeginInvoke(new UpdateStatus(SetStatus), p); 
    } 

    private object[] GetInokerPara(int progress, string msg, Color color) 
    { 
     object[] para = new object[3]; 
     para[0] = progress; 
     para[1] = msg; 
     para[2] = color; 

     return para; 
    } 
} 

乙级

class b 
{ 
    public delegate void SetStatus(int i, string msg, Color color); 
    public event SetStatus SetStatusEvent; 

    System.Diagnostics.Process usbfor = new System.Diagnostics.Process(); 

    public void FormatUSB() 
    { 

     usbfor.StartInfo.FileName = @"usbformat.bat"; 
     usbfor.EnableRaisingEvents = true; 
     usbfor.Exited += new EventHandler(usbforProcessExited); 
     usbfor.Start(); 
    } 

    public void usbforProcessExited(object sender, EventArgs f) 
    { 
     SetStatusEvent(100, "DONE", Color.Green); //ERROR HERE! (object reference not set to an instance of an object 
    } 
} 

问题在哪里?

+0

你应该学会产生最少的工作例子。你的代码包含很多与这个问题无关的东西。 – CodesInChaos

+0

我不认为这是你的问题的原因,但你不需要“usbfor.EnableRaisingEvents = true”的过程来提高退出事件? –

+0

@ChrisSpicer OPS!其实我有这条线!我只是想尽量减少我发布的代码量。谢谢你提到那个部分。 – daygoor

回答

3

你有一个竞争条件:

usbforProcessExited被认购在b构造函数,你叫rsSet.SetStatusEvent += new RemoteS.SetStatus(updateStatus)之前可能会被调用。

订阅SetStatusEvent后,您只能拨打usbfor.Start()

一个相关的问题是该事件将在另一个线程上运行。您应该在开始处理之前设置rsSet.SynchronizingObject,以便您的事件处理程序可以修改表单,而无需手动调用Invoke/BeginInvoke

1

事件是null,如果没有人订阅它。 所以这是一个很好的做法,对null平等控制,如:

public void usbforProcessExited(object sender, EventArgs f) 
    { 
     if(SetStatusEvent!=null) 
      SetStatusEvent(100, "DONE", Color.Green); 
    } 

这,就是为什么它工作得很好,你有,这条线:

rsSet.SetStatusEvent += new RemoteS.SetStatus(updateStatus); 

所以认购及的initilizaton事件。

当您从内部呼叫时,没有任何订阅,所以事件是null

编辑

继意见,让我们提供的有关事件处理的空引用检查多个线程安全的方法:

public void usbforProcessExited(object sender, EventArgs f) 
    { 
     var ev = SetStatusEvent; //[1] 
     if(ev!=null) //[2] 
      ev(100, "DONE", Color.Green); 
    } 

记住,赋值操作IA原子在CLR,所以即使在线路之间。 1]和[2]其他人将事件重置为空,则您的ev仍然有效,代码将执行而不会崩溃。如果这是需要的行为,它取决于您的具体情况,所以这只是另一个选项,以线程安全的方式管理事件的空引用控制。

+2

除了不是线程安全的 - 如果最后一个订阅者在检查和调用之间取消订阅,您最终会得到一个NullReferenceException。这可能是也可能不是问题,具体取决于您想要支持的线程。 –

+0

我通常做'var handler = SetStatusEvent;'第一(尽管我找不到引用的原因) – Default

+0

空检查(即使正确完成)导致OP代码中丢失的事件。所以这不是解决方案。 – CodesInChaos

6

如果没有订户,则事件为空。

解决办法有两个:

  1. 声明(虚拟用户什么都不做),当初始化事件:

    public event SetStatus SetStatusEvent = delegate { }; 
    
  2. 提高(在一个线程安全的方式之前,检查是否为空的情况下):

    public void usbforProcessExited(object sender, EventArgs f) 
    { 
        SetStatus setStatus = SetStatusEvent; 
        if (setStatus != null) 
        { 
         setStatus(100, "DONE", Color.Green); 
        } 
    } 
    
+0

空检查(即使正确完成)导致OP代码中丢失的事件。所以这不是解决方案。 – daygoor

+0

在这种情况下,空检查只会隐藏症状。 –

+0

@daygoor在编写你的Raise事件方法时,你仍然应该使用这个模式。正如你用自己的话说的那样(这是因为你是OP而感兴趣),“如果正确完成”。 – Default

1

Jon Skeet教会了我,在c#6.0中,你也可以使用:

SetStatusEvent?.Invoke(100, "DONE", Color.Green);; 
相关问题