1

I'm trying to do a windows app and I have a function that takes several minutes to complete the task. I have a start button and I'd like to add a stop button in order to stop the processing of the function whenever I want to stop it.

I'm trying with the code below, but I'm not sure how to abort the Thread1 inside btnStop since Thread1 is marked as "does not exists in current context".

May you please suggest me/point me in rigth direction in how would be a good way to do this. Thanks in advance.

namespace SampleStartStop
{    
    public partial class Form1 : Form
    {        
        public Form1()
        {
            InitializeComponent();            
        }

        private void btnStart_Click(object sender, EventArgs e)
        {
            Thread Thread1 = new Thread(SlowFunction);            
            Thread1.Start();                        
        }

        private void btnStop_Click(object sender, EventArgs e)
        {
            Thread1.Abort();
            MessageBox.Show("Processing canceled");
        }
        public void SlowFunction()
        {
            var end = DateTime.Now + TimeSpan.FromSeconds(10);
            while (DateTime.Now < end)
            { }
            MessageBox.Show("Process finished");
        }

    }
}

Update: Hi KCdod, thanks for your help, When I only declare thread as global variable I get "An unhandled exception of type 'System.NullReferenceException' occurred in SampleStartStop.exe".

Hi Alexei, thanks for the correction. Thanks zerkms and Alexei for share about cancellation tokens. Following the example in link you shared I was able to write the code below. It seems to work but I'd like the approbal of you experts if it needs some change or if it is fine.

The only doubt regarding the current code is, if Stop button is pressed it stops the processing fine, but if I click start button again, nothing happens and I need to close and open again the App in order to get working again start button, is this normal?

The other doubt is in the part inside "The listener". In MSDN example they put "// Perform cleanup if necessary.", so, what kind of clean up they are talking about?

public partial class Form1 : Form
{       
    public Form1()
    {
        InitializeComponent();            
    }
    // Create the token source.
    CancellationTokenSource cts = new CancellationTokenSource();

    private void btnStart_Click(object sender, EventArgs e)
    {
        // Pass the token to the cancelable operation.
        ThreadPool.QueueUserWorkItem(new WaitCallback(SlowFunction), cts.Token);                       
    }

    private void btnStop_Click(object sender, EventArgs e)
    {
        // Request cancellation.
        cts.Cancel();
        // Cancellation should have happened, so call Dispose.
        cts.Dispose();

        MessageBox.Show("Processing canceled");
    }
    public void SlowFunction(object obj)
    {
        CancellationToken token = (CancellationToken)obj;

        var end = DateTime.Now + TimeSpan.FromSeconds(10);
        while (DateTime.Now < end)
        {
            // Thread 2: The listener 
            if (token.IsCancellationRequested)
            {
                // Perform cleanup if necessary. 
                //... 
                // Terminate the operation.                  
                break;
            }
        }
        if (!token.IsCancellationRequested)
        {
            MessageBox.Show("Processing finished");
        }
    }

}

Update: Thanks Alexei for your correction, I've modified the code with your suggestions and this time works nice. the code is as below. I only have an issue since in my real code, the Function needs a string argument to work and I don't know how to call it inside the part "WaitCallback(SlowFunction)" and how to define the function in the code, since here is defined like this "public void SlowFunction(object obj) {...}" and in my real function is like this "public void SlowFunction(string str)". I think that I'd need to ask in a new question this issue.

namespace SampleStartStop
{    
    public partial class Form1 : Form
    {       
        // Create the token source.
        CancellationTokenSource cts = new CancellationTokenSource();

        public Form1()
        {
            InitializeComponent();            
        }

        private void btnStart_Click(object sender, EventArgs e)
        {
            if (cts != null)
            {
                cts.Cancel();
            }           
            // Pass the token to the cancelable operation.
            cts = new CancellationTokenSource();
            ThreadPool.QueueUserWorkItem(new WaitCallback(SlowFunction), cts.Token);                         
        }

        private void btnStop_Click(object sender, EventArgs e)
        {
            if (cts != null)
            {
                cts.Cancel();
                cts = null;
                MessageBox.Show("Processing canceled");
            }   
        }
        public void SlowFunction(object obj)
        {
            CancellationToken token = (CancellationToken)obj;

            var end = DateTime.Now + TimeSpan.FromSeconds(5);
            while (DateTime.Now < end)
            {
                if (token.IsCancellationRequested)
                {                
                    break;
                }
            }
            if (!token.IsCancellationRequested)
            {
                MessageBox.Show("Processing finished");
            }
        }

    }
}
Sarmeu
  • 95
  • 1
  • 3
  • 9

2 Answers2

4

There is no good way to terminate thread that is not cooperating. Indeed Thread.Abort will do that but at price of potentially leaving non-disposed objects and abandoned synchronization primitives thus potentially destabilizing your program.

Fix for your immediate problem - move Thread1 to be class level member instead of local variable. You'll need to check if it is already set/cleared:

public partial class Form1 : Form {
   ...

  Thread thread1 = null;            

  private void btnStart_Click(object sender, EventArgs e)
  {
    if (thread1 != null)
    {
         thread1.Abort();
    }
    thread1 = new Thread(SlowFunction);            
    Thread1.Start();                        
  }

  private void btnStop_Click(object sender, EventArgs e)
  {
    if (thread1 != null)
    {
         thread1.Abort();
         thread1 = null;
         MessageBox.Show("Processing canceled");
    }
}

It will be much better if you can make "slow function" to cooperate in termination - i.e. by periodically checking some value. Check Cancellation tokens for .Net way of doing so.

Alexei Levenkov
  • 98,904
  • 14
  • 127
  • 179
  • Hi Alexei, many thanks for your help. May you please see my update in original post. – Sarmeu Jan 06 '15 at 05:30
  • @Sarmeu you did not copy recreation of objects from my sample and hence your cancelation token stays canceled (and even disposed) forever instead of starting anew on every start of the thread. – Alexei Levenkov Jan 06 '15 at 06:30
  • Hi Alexei, what do you mean recreation of objects? I tested the sample you posted and worked. When you said that would be better to make the function to cooperate in cancel operation, I only understood I had to do similar as they do in link you shared. Then, what do you suggest is a mix between your sample and my second code? Thanks again – Sarmeu Jan 06 '15 at 06:39
  • 1
    @Sarmeu you call `.Dispose()` on cancelation token but keep using it for new thread start instead of immediately setting it to `null` after dispose call. As result whenever next thread checks if it needs to be canceled it immediately get "cancel now". You should create new token when you start new thread (still keeping it as member variable since you need it later for canceling). – Alexei Levenkov Jan 06 '15 at 06:46
  • Hello Alexei, many thanks for your suggestions. Please see my update in original post. Doing as you suggested it works nice. But I have another issue since my real "SlowFunction" needs a string argument to work. Thanks again – Sarmeu Jan 06 '15 at 19:38
  • 1
    @Sarmeu [Chameleon question](http://meta.stackoverflow.com/questions/253762/link-for-poor-or-ever-growing-questions-to-better-explain-why-people-stop-answer) is generally bad fit for SO. Please see if your original problem solved and ask new questions separately. – Alexei Levenkov Jan 06 '15 at 19:53
  • Hi Alexei. Exactly. Was not my intention to ask a new thing, this issue appeared when I realized that "WaitCallback(SlowFunction)" doesn't accept to put arguments like "WaitCallback(SlowFunction(MyString))". I'll ask this in a new separate question. Thanks so much for your great help. Regards – Sarmeu Jan 06 '15 at 20:01
1

You can declare your thread, Thread Thread1; as global variable. In your current code your Thread1 scope limits to btnStart_Click() event function.

namespace SampleStartStop
{    
    public partial class Form1 : Form
    {   
        Thread Thread1=null;
        public Form1()
        {
            InitializeComponent();            
        }

        private void btnStart_Click(object sender, EventArgs e)
        {
            Thread1 = new Thread(SlowFunction);            
            Thread1.Start();                        
        }

        private void btnStop_Click(object sender, EventArgs e)
        {
            Thread1.Abort();
            MessageBox.Show("Processing canceled");
        }
        public void SlowFunction()
        {
            var end = DateTime.Now + TimeSpan.FromSeconds(10);
            while (DateTime.Now < end)
            { }
            MessageBox.Show("Process finished");
        }

    }
}

Additional - Thread abort is not GOOD but you can use it.

Community
  • 1
  • 1
Kavindu Dodanduwa
  • 12,193
  • 3
  • 33
  • 46