Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing argument into backgroundWorker (for use as a Cancel button)

I'm new to C# and object-oriented programming in general. I've been trying to implement a "Cancel" button into my GUI so that the user can stop it mid-process.

I read this question: How to implement a Stop/Cancel button? and determined that a backgroundWorker should be a good option for me, but the example given doesn't explain how to hand arguments to the backgroundWorker.

My problem is that I do not know how to pass an argument into backgroundWorker such that it will stop the process; I have only been able to get backgroundWorker to stop itself.

I created the following code to try to learn this, where my form has two buttons (buttonStart and buttonStop) and a backgroundWorker (backgroundWorkerStopCheck):

using System;
using System.ComponentModel;
using System.Windows.Forms;
using System.Threading;
using System.Timers;

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

            // Set the background worker to allow the user to stop the process. 
            backgroundWorkerStopCheck.WorkerSupportsCancellation = true;
        }

        private System.Timers.Timer myTimer;

        private void backgroundWorkerStopCheck_DoWork(object sender, DoWorkEventArgs e)
        {
            //If cancellation is pending, cancel work.  
            if (backgroundWorkerStopCheck.CancellationPending)
            {
                e.Cancel = true;
                return;
            }
        }

        private void buttonStart_Click(object sender, EventArgs e)
        {
            // Notify the backgroundWorker that the process is starting.
            backgroundWorkerStopCheck.RunWorkerAsync();
            LaunchCode();
        }

        private void buttonStop_Click(object sender, EventArgs e)
        {
            // Tell the backgroundWorker to stop process.
            backgroundWorkerStopCheck.CancelAsync();
        }

        private void LaunchCode()
        {
            buttonStart.Enabled = false; // Disable the start button to show that the process is ongoing.
            myTimer = new System.Timers.Timer(5000); // Waste five seconds.
            myTimer.Elapsed += new ElapsedEventHandler(myTimer_Elapsed);
            myTimer.Enabled = true; // Start the timer.
        }

        void myTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            buttonStart.Enabled = true; // ReEnable the Start button to show that the process either finished or was cancelled.
        }
    }
}

The code, if it worked properly, would just sit there for five seconds after the user clicked "Start" before re-enabling the Start button, or would quickly reactivate the Start button if the user clicked "Stop".

There are two problems with this code that I am not sure how to handle:

1) The "myTimer_Elapsed" method results in an InvalidOperationException when it attempts to enable the Start button, because the "cross-thread operation was not valid". How do I avoid cross-thread operations?

2) Right now the backgroundWorker doesn't accomplish anything because I don't know how to feed arguments to it such that, when it is canceled, it will stop the timer.

I'd appreciate any assistance!

like image 620
Patrigon Avatar asked Feb 22 '23 09:02

Patrigon


1 Answers

First of all, the problem to avoid "cross-thread operation was not valid" is use Invoke on controls. You cannot use a control from a different thread.

About the second issue, I would implement it in the following way. This is a minimum background worker implementation with cancel support.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

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

            // Set the background worker to allow the user to stop the process. 
            backgroundWorkerStopCheck.WorkerSupportsCancellation = true;
            backgroundWorkerStopCheck.DoWork += new DoWorkEventHandler(backgroundWorkerStopCheck_DoWork);
        }

        private void backgroundWorkerStopCheck_DoWork(object sender, DoWorkEventArgs e)
        {
            try
            {
                for (int i = 0; i < 50; i++)
                {
                    if (backgroundWorkerStopCheck.CancellationPending)
                    {
                        // user cancel request
                        e.Cancel = true;
                        return;
                    }

                    System.Threading.Thread.Sleep(100);
                }
            }
            finally
            {
                InvokeEnableStartButton();
            }
        }

        private void buttonStart_Click(object sender, EventArgs e)
        {
            //disable start button before launch work
            buttonStart.Enabled = false;

            // start worker
            backgroundWorkerStopCheck.RunWorkerAsync();
        }

        private void buttonStop_Click(object sender, EventArgs e)
        {
            // Tell the backgroundWorker to stop process.
            backgroundWorkerStopCheck.CancelAsync();
        }

        private void InvokeEnableStartButton()
        {
            // this method is called from a thread,
            // we need to Invoke to avoid "cross thread exception"
            if (this.InvokeRequired)
            {
                this.Invoke(new EnableStartButtonDelegate(EnableStartButton));
            }
            else
            {
                EnableStartButton();
            }
        }

        private void EnableStartButton()
        {
            buttonStart.Enabled = true;
        }
    }

    internal delegate void EnableStartButtonDelegate();
}

About passing arguments to the worker, you can pass any object in the RunWorkerAsync() method, and its reveived in the backgroundWorkerStopCheck_DoWork method:

  ...
  backgroundWorkerStopCheck.RunWorkerAsync("hello");
  ...

  private void backgroundWorkerStopCheck_DoWork(object sender, DoWorkEventArgs e)
  {
      string argument = e.Argument as string;
      // argument value is "hello"
      ...
  }

Hope it helps.

like image 103
Daniel Peñalba Avatar answered Feb 25 '23 02:02

Daniel Peñalba