3

I have a question about using threads. I have an application in WinForms. I have a start button with a method. When I click on it the method starts performing and lasts for a long time. When the method is performing the Form is not active I can't even close it until the method ends. I want to make the Form active and click on another button (Stop button) to stop method performing.

    private void start_Click(object sender, EventArgs e)
    {
        StartLoading() //Some Method which performing I want to stop at any time
    }

    private void stop_Click(object sender, EventArgs e)
    {
        //Stop performing Method from start_Click
    }

I tryed to use the next code:

    private void start_Click(object sender, EventArgs e)
    {
        Thread StartThread = new Thread(StartLoading);
        StartThread.Start();
    }

    public void StartLoading() 
    {
    }

And it works. The method is performing while the Form remains active. But I don't know how to stop this thread on stop_Click event. Maye there is another way to do what I want??

Best Regards Sergey

Sergey
  • 103
  • 1
  • 2
  • 9

3 Answers3

4

If you want a slow action to be executed until it is finished or until you manually cancel it, you may want to use Task and CancellationToken. For me it seems like the most proper way to do this:

public class MyForm
{
    private CancellationTokenSource cts = new CancellationTokenSource();
    private Task task;

    private void buttonStart_Click(object sender, EventArgs e) 
    {
        buttonStart.Enabled = false;
        buttonCancel.Enabled = true;

        task = Task.Factory.StartNew(() => {
            // do something extremely slow
            // and use 'ThrowIfCancellationRequested'

            for (int i = 0; i < Int32.MaxValue; i++)
            {
                Thread.Sleep(10);
                cts.Token.ThrowIfCancellationRequested();
            }
        }, cts.Token).ContinueWith(t => {
            if (t.IsCanceled)
            {
                // User has cancelled loading
            }
            if (t.IsFaulted)
            {
                // Exception has occured during loading
            }
            if (t.IsCompleted)
            {
                // Loading complete
            }
        });
    }       

    private void buttonCancel_Click(object sender, EventArgs e)
    {
        buttonStart.Enabled = true;
        buttonCancel.Enabled = false;

        cts.Cancel();
    }
}
Yeldar Kurmangaliyev
  • 33,467
  • 12
  • 59
  • 101
  • I think you need to use `cts.Token.ThrowIfCancellationRequested()` not `if (cts.IsCancellationRequested) return;` if you want `t.IsCanceled` to return true if you cancel after the task starts. If you don't the task thinks the cancellation was requested but the method completed it's work so it marks it as `IsCompleted`. – Scott Chamberlain Dec 08 '15 at 07:00
  • @ScottChamberlain Of course, you are right. I have edited my answer. Am I missing `try .. catch` somewhere or will it properly handle `OperationCanceledException` itself? Thank you. – Yeldar Kurmangaliyev Dec 08 '15 at 07:03
  • If the OperationCanceledException is associated with the same token that was passed in as the 2nd argument to `StartNew` it is treated as a special case and goes to the IsCanceled state instead of the IsFaulted state. If a OperationCanceledException is raised but it was not associated with the passed in token it will show up as a faulted task. – Scott Chamberlain Dec 08 '15 at 07:05
  • See the MSDN page "[How to: Cancel a Task and Its Children](https://msdn.microsoft.com/en-us/library/dd537607(v=vs.110).aspx)" for a detailed example. – Scott Chamberlain Dec 08 '15 at 07:07
2

If you're doing some looping inside the thread, I would suggest you to add a variable, like this:

In your stop button, add below:

bool isStopped = false;

And inside your loop:

while(yourCondition)
{
    if(isStopped)
       break;
}

This way, it's safer and can make sure you finish current loop

But if you would like to terminate it immediately, there's a function called Thread.Abort()

You'll have to modify the code like this:

Thread StartThread = null;
private void start_Click(object sender, EventArgs e)
{
    StartThread = new Thread(StartLoading);
    StartThread.Start();
}

public void StartLoading() 
{
    StartThread.Abort();
}
User2012384
  • 4,769
  • 16
  • 70
  • 106
  • 1
    Note that calling `StartThread.Abort()` will raise an exception, which should be caught. – Yeldar Kurmangaliyev Dec 08 '15 at 06:30
  • 1
    Thanks. Your answer works. It's easy and very simple. All other answers are too difficult to understand for me)) – Sergey Dec 08 '15 at 10:59
  • 2
    @Sergey `Thread.Abort()` [is a horrible choice](http://stackoverflow.com/questions/1559255/whats-wrong-with-using-thread-abort), it can leave your program in a corrupted state. Don't do it. – Scott Chamberlain Dec 08 '15 at 14:35
  • Yes so I put a workaround, and told op if he really needs to terminate the thread immediately, then use this method – User2012384 Dec 08 '15 at 14:38
  • 1
    I think that Thread.Abort() is good for me. Because I will use it very seldom and only when something goes wrong and I want to stop program immediately, I will not care about its workability. Thanks! – Sergey Dec 09 '15 at 08:00
1

You can use a BackgroundWorker for this.

        private BackgroundWorker bw = new BackgroundWorker();

        public Form()
        {
            InitializeComponent();

            bw.WorkerSupportsCancellation = true;
            bw.DoWork += new DoWorkEventHandler(bw_DoWork);
            bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
        }

        private void start_Click(object sender, EventArgs e)
        {
            if (!bw.IsBusy)
            {
                bw.RunWorkerAsync();
            }
        }
        private void stop_Click(object sender, EventArgs e)
        {
            if (bw.WorkerSupportsCancellation)
            {
                bw.CancelAsync();
            }
        }
        private void bw_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker worker = sender as BackgroundWorker;

            if (worker.CancellationPending)
            {
                e.Cancel = true;
                return;
            }

            StartLoading(); //Some Method which performing I want to stop at any time
        }
        private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Cancelled)
            {
                //"Canceled!";
            }

            else if (e.Error != null)
            {
                //"Error: " + e.Error.Message);
            }

            else
            {
                //"Done!";
            }
        }
Irshad
  • 3,071
  • 5
  • 30
  • 51