2013-07-31 39 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);; 
相關問題