简体   繁体   中英

C# - Thread Abort and System.NullReferenceException

I am doing a practise GUI Oven program using a thread, I am not sure if I should even be doing this because I want to interact with the GUI when the Heating process is ongoing. When I try to abort the thread by click btnStop_Click, it throws the NullReference exception:

System.NullReferenceException: Object reference not set to an instance of an object.

Please advice on how can I gracefully stop the thread. Thanks.

Code:

 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);
                }
            }
        }
    }

You are creating a new instance of Form1 in your stop click event and so you are talking to a completely different t from the one in your start click.

You also probably want to have a single instance of Heat that you assign in heatIt and then use that reference in your stop click.

Also for background processing you probably want to look at the BackgroundWorker class to do the heavy lifting for you.

Several remarks:

  1. You should never use Thread.Abort to stop background tasks. This is a bad practice, as it forces aborting the background thread regardless of its state. Use a volatile bool flag instead, and check (every once in a while) if its value has changed.

  2. It seems that your Form represents a UI for business logic extracted into a separate class ( Heating ). In that case, it probably makes sense to have only a single instance per form, and put it in a private field. Right now you are creating a new instance inside your Stop method, which is probably wrong (since I presume you already use it in the heatIt method).

  3. For each Subscribe method, try to keep a habit of adding a Unsubscribe method, which detaches event handlers at some point. This way GC can collect your listeners after they are no longer needed, and you prevent adding the same event handlers several times.

I would expect something like:

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);
}

And, in your background worker method, you will need to have a loop which checks if _stopRequest has been set:

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

Note that you must have a place in your worker method which will check the _stopRequest flag. Otherwise the only way to stop it is to Abort it (like you did), which is not recommended .

Apart from that, you don't need to stop the thread (like you did in your SignalStop method) once the process is finished. When HeatIt method returns (ends), the thread will also end, and there is no need to do this.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM