简体   繁体   English

C# - 线程中止和 System.NullReferenceException

[英]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.我正在使用线程做一个练习 GUI Oven 程序,我不确定我是否应该这样做,因为我想在加热过程正在进行时与 GUI 交互。 When I try to abort the thread by click btnStop_Click, it throws the NullReference exception:当我尝试通过单击 btnStop_Click 中止线程时,它会引发 NullReference 异常:

System.NullReferenceException: Object reference not set to an instance of an object. System.NullReferenceException:Object 引用未设置为 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.您正在停止单击事件中创建 Form1 的新实例,因此您正在与与开始单击中的 t 完全不同的 t 交谈。

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.您可能还希望拥有一个在 heatIt 中分配的 Heat 实例,然后在停止单击中使用该引用。

Also for background processing you probably want to look at the BackgroundWorker class to do the heavy lifting for you.同样对于后台处理,您可能希望查看BackgroundWorker class来为您完成繁重的工作。

Several remarks:几点说明:

  1. You should never use Thread.Abort to stop background tasks.您永远不应该使用Thread.Abort来停止后台任务。 This is a bad practice, as it forces aborting the background thread regardless of its state.这是一个不好的做法,因为它会强制中止后台线程,而不管其 state。 Use a volatile bool flag instead, and check (every once in a while) if its value has changed.改为使用volatile bool标志,并检查(每隔一段时间)其值是否已更改。

  2. It seems that your Form represents a UI for business logic extracted into a separate class ( Heating ).您的表单似乎代表了提取到单独的 class ( Heating )中的业务逻辑 UI。 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).现在您正在您的Stop方法中创建一个新实例,这可能是错误的(因为我假设您已经在heatIt方法中使用它)。

  3. For each Subscribe method, try to keep a habit of adding a Unsubscribe method, which detaches event handlers at some point.对于每个Subscribe方法,请尝试保持添加Unsubscribe方法的习惯,该方法在某些时候分离事件处理程序。 This way GC can collect your listeners after they are no longer needed, and you prevent adding the same event handlers several times.这样,GC 可以在不再需要您的侦听器后收集它们,并且您可以防止多次添加相同的事件处理程序。

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:而且,在您的后台工作方法中,您需要有一个循环来检查_stopRequest是否已设置:

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

Note that you must have a place in your worker method which will check the _stopRequest flag.请注意,您的工作方法中必须有一个位置来检查_stopRequest标志。 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.除此之外,您不需要在进程完成后停止线程(就像您在SignalStop方法中所做的那样)。 When HeatIt method returns (ends), the thread will also end, and there is no need to do this.HeatIt方法返回(结束)时,线程也将结束,无需这样做。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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