C# - Thread Abort和System.NullReferenceException

时间:2011-08-16 09:06:40

标签: c# multithreading nullreferenceexception

我正在使用线程进行练习GUI烤箱程序,我不确定我是否应该这样做,因为我想在加热过程正在进行时与GUI进行交互。当我尝试通过单击btnStop_Click来中止线程时,它会抛出NullReference异常:

System.NullReferenceException:对象引用未设置为对象的实例。

请告知我如何优雅地停止线程。感谢。

代码:

 public partial class Form1 : Form
        {
            private Thread t;

            public Form1()
            {
                InitializeComponent();
            }
            // button to begin heating
            private void btnStart_Click(object sender, EventArgs e)
            {   
                if ((txtMin.Text) == "" || (txtSec.Text) == "")
                {
                    MessageBox.Show("Please enter duration of heating");
                }
                else
                {
                    t = new Thread(heatIt);
                    btnHeat.Enabled = false;
                    t.Start();
                }
            }

           //stop heating
            private void btnStop_Click(object sender, EventArgs e)
            {
                Heating heat = new Heating();
                Form1 l = new Form1();
                l.Subscribe(heat);
                heat.stopHeat();
                btnHeat.Enabled = true;
            }

            private void heatIt()
            {
            // heat food Implementation that calls the 'Heating' class
            }

           public void Subscribe(Heating m)
           {
            m.heatComplete += SignalHeatCompleted;
            m.heatStop += SignalStop;
           }

           private void SignalHeatCompleted(Heating m, EventArgs e)
           {
            MessageBox.Show( "Done, please enjoy your food");
            return;
           }

           private void SignalStop(Heating m, EventArgs e)
           {
           t.Abort();
            MessageBox.Show("Heating Terminated");
            return;            
           }

public class Heating
        {
            public event HeatingCompleted heatComplete; // Heating Completed Event
            public event HeatingStop heatStop;          // Heating Stop Event
            public EventArgs e = null;
            public delegate void HeatingCompleted(Heating h, EventArgs e);
            public delegate void HeatingStop(Heating s, EventArgs e);

            public void startHeat(int temp, int min, int sec)
            {
                int totalSec;
                totalSec = ((min*60) + sec) * 1000;

                Thread.Sleep(totalSec);
                if (heatComplete != null)
                {
                    heatComplete(this, e);
                }
                else
                {
                    //Use default signal if there's no subscription to this event
                    MessageBox.Show("*TING*");

                }
                return;    
            }

            public void stopHeat()
            {
                if (heatStop != null)
                {
                    heatStop(this, e);
                }
            }
        }
    }

2 个答案:

答案 0 :(得分:2)

您正在停止点击活动中创建一个新的Form1实例,因此您与开始点击中的实例完全不同。

您可能还希望在heatIt中分配一个Heat实例,然后在停止点击中使用该引用。

同样对于后台处理,您可能希望查看BackgroundWorker class来为您做繁重的工作。

答案 1 :(得分:1)

几条评论:

  1. 您绝不能使用Thread.Abort来停止后台任务。这是一种不好的做法,因为它强制中止后台线程而不管其状态如何。改为使用volatile bool标志,并检查(每隔一段时间)其值是否已更改。

  2. 您的表单似乎表示业务逻辑的UI被提取到单独的类(Heating)中。在这种情况下,每个表单只有一个单个实例可能是有意义的,并将其放在私有字段中。现在你正在Stop方法中创建一个新实例,这可能是错误的(因为我认为你已经在heatIt方法中使用它了。)

  3. 对于每个Subscribe方法,请尝试保留添加Unsubscribe方法的习惯,该方法会在某个时刻分离事件处理程序。通过这种方式,GC可以在不再需要后收集监听器,并且可以防止多次添加相同的事件处理程序。

  4. 我希望有类似的东西:

    private Heating _heating;
    private Thread _workerThread;
    private volatile bool _stopRequest = false;
    
    void Start_Btn_Pressed(object sender, EventArgs e)
    {
        // create the private instance
        _heating = new Heating();
        Subscribe(_heating);
    
        // start the thread
        _stopRequest = false;
        _workerThread = new Thread(HeatIt);
        _workerThread.Start();
    }
    
    void Stop_Btn_Pressed(object sender, EventArgs e)
    {
        // request stop
        _stopRequest = true;
    
        // wait until thread is finished
        _workerThread.Join();
    
        // unsubscribe
        // ** note that a better place for unsubscribing 
        //    might be at the end of the HeatIt method
        Unsubscribe(_heating);
    }
    

    并且,在后台工作器方法中,您需要有一个循环来检查是否已设置_stopRequest

    void HeatIt()
    {
        while (!_stopRequest && !finishedWork)
        {
            // do work
        }
    }
    

    请注意,必须在您的worker方法中有一个位置,该位置将检查_stopRequest标志。否则,阻止它的唯一方法是中止它(就像你一样),which is not recommended

    除此之外,一旦完成该过程,您不需要停止该线程(就像您在SignalStop方法中所做的那样)。当HeatIt方法返回(结束)时,线程也将结束,并且不需要执行此操作。