progrAmmar
progrAmmar

Reputation: 2670

Stopping / cancelling BackgroundWorker

I have created an application which generates multiple background workers. The premise is that I have a datagridview and in that I have a button column.

The button column changes it's name to Start and Stop.

Following is the code for the button click

private void dgConfig_CellClick(object sender, DataGridViewCellEventArgs e)
    {
        // Ignore clicks that are not on button cells.  
        string row = e.RowIndex.ToString();
        if (e.RowIndex < 0 || e.ColumnIndex > dgConfig.Columns[1].Index) 
            return;
        if (e.ColumnIndex == 0 && dgConfig.Rows[e.RowIndex].Cells[0].Value.ToString().Equals("Start"))
        {
            dgConfig.Rows[e.RowIndex].Cells[0].Value = "Stop";
            string input = string.Empty;
            string output = string.Empty;
            Dictionary<string, string> args = GenerateCommands(e.RowIndex);

           dgConfig.Rows[e.RowIndex].Cells[0].Value.ToString());
            StartThread(e.RowIndex.ToString(), "", "", args);

        }
        else if (e.ColumnIndex == 0 && dgConfig.Rows[e.RowIndex].Cells[0].Value.ToString().Equals("Stop"))
        {
            dgConfig.Rows[e.RowIndex].Cells[0].Value = "Start";


            if (processes.Count > 0)
            {
                int procId = int.Parse(processes[e.RowIndex.ToString()]);
                Process p = Process.GetProcessById(procId);
                MessageBox.Show("Stopping Process "+p.ProcessName);
                p.Close();

                p = null;

            }
    }
}

Here is the start thread method.

private void StartThread(string row, string input, string output, Dictionary<string, string> commands)
    {

        BackgroundWorker background = new BackgroundWorker();
        background.DoWork += new DoWorkEventHandler(bkWorker_DoWork);
        background.WorkerReportsProgress = false;
        background.WorkerSupportsCancellation = true;            
        background.RunWorkerAsync(commands);

    }

and finally this is the Worker process

 private void bkWorker_DoWork(object sender, DoWorkEventArgs e)
    {
        Dictionary<string, string> inputs = e.Argument as Dictionary<string, string>;
        string newcmd = @"C:\mympeg.exe";

        foreach (KeyValuePair<string, string> val in inputs )
        {
            newcmd += " " + val.Key + " " + val.Value;
        }

        Process proc = new Process();
        proc.StartInfo.FileName = "cmd.exe";
        proc.StartInfo.UseShellExecute = false;
        proc.StartInfo.CreateNoWindow = true;
        proc.StartInfo.RedirectStandardOutput = true;
        proc.StartInfo.RedirectStandardInput = true;
        proc.StartInfo.RedirectStandardError = true;
        proc.OutputDataReceived += new DataReceivedEventHandler(SortOutputHandler);
        proc.ErrorDataReceived += new DataReceivedEventHandler(ErrorOutputHandler);
        proc.Start();
        processes.Add(row, proc.Id.ToString());
        if (txtLog.InvokeRequired)
        {
            this.Invoke((MethodInvoker)delegate { 
                txtLog.AppendText("Starting Process......\r\n");
                txtLog.AppendText("Command recieved: "+newcmd+"\r\n"); 
            });
        }
        else
        {
            txtLog.AppendText("Starting Process......\r\n");
            txtLog.AppendText("Command recieved: " + newcmd + "\r\n"); 
        }
        StreamWriter cmdStreamWriter = proc.StandardInput;
        proc.BeginOutputReadLine();
        proc.BeginErrorReadLine();
        cmdStreamWriter.WriteLine(newcmd);
        cmdStreamWriter.Close();
        proc.WaitForExit();
        if (txtLog.InvokeRequired)
        {
            this.Invoke((MethodInvoker)delegate { txtLog.AppendText("\r\nFinished Process.......\r\n"); });
        }
        else
        {
            txtLog.AppendText("\r\nFinished Process.......\r\n");
        }
        proc.Close();
    }

So if I start a long running process, the Process starts, I tried suspending threads, but that doesn't work. I want to be able to Stop the specific background worker (not all).

Can I close one background worker on button click corresponding to data in that row?

EDIT

Is there anyway I can make a method called process.WaitForEvent() and pass the button click function to it?

Upvotes: 0

Views: 168

Answers (2)

lezhkin11
lezhkin11

Reputation: 414

You are just killing process - not stopping backgroundworker. You have to use BackgroundWorker.CancelAsync()

In your case probably: - Create a Dictionary at class level

private Dictionary<string, BackgroundWorker> workers= new Dictionary<string, BackgroundWorker>();

- in StartThread add background worker to it like

this.workers.Add(row, background);

- To stop worker use

BackgroundWorker worker = this.workers[e.RowIndex.ToString()];
worker.CancelAsync();

EDIT:

private void bkWorker_DoWork(object sender, DoWorkEventArgs e)
{
    Dictionary<string, string> inputs = e.Argument as Dictionary<string, string>;
    string newcmd = @"C:\mympeg.exe";

    BackgrounWorker instance = sender as BackgroundWorker;

    if (instance == null)
    {
       e.Cancel = true;
       return;
    }

    Process proc = new Process();
    proc.StartInfo.FileName = "cmd.exe";
    proc.StartInfo.UseShellExecute = false;
    proc.StartInfo.CreateNoWindow = true;
    proc.StartInfo.RedirectStandardOutput = true;
    proc.StartInfo.RedirectStandardInput = true;
    proc.StartInfo.RedirectStandardError = true;
    proc.OutputDataReceived += new DataReceivedEventHandler(SortOutputHandler);
    proc.ErrorDataReceived += new DataReceivedEventHandler(ErrorOutputHandler);
    proc.Start();
    processes.Add(row, proc.Id.ToString());
    if (txtLog.InvokeRequired)
    {
        this.Invoke((MethodInvoker)delegate { 
            txtLog.AppendText("Starting Process......\r\n");
            txtLog.AppendText("Command recieved: "+newcmd+"\r\n"); 
        });
    }
    else
    {
        txtLog.AppendText("Starting Process......\r\n");
        txtLog.AppendText("Command recieved: " + newcmd + "\r\n"); 
    }
    StreamWriter cmdStreamWriter = proc.StandardInput;
    proc.BeginOutputReadLine();
    proc.BeginErrorReadLine();

    cmdStreamWriter.Write(newcmd);

    foreach (KeyValuePair<string, string> val in inputs )
    {
        cmdStreamWriter.Write(" " + val.Key + " " + val.Value);

        if(instance.CancellationPending)
        {
            break;
        }
    }

    cmdStreamWriter.Close();

    if (txtLog.InvokeRequired)
    {
        this.Invoke((MethodInvoker)delegate { txtLog.AppendText("\r\nFinished Process.......\r\n"); });
    }
    else
    {
        txtLog.AppendText("\r\nFinished Process.......\r\n");
    }
    proc.Close();
}

Upvotes: 0

Alioza
Alioza

Reputation: 1770

You have to keep your workers in order to stop them. Save them in a dictionary with the identifier of the row as a key:

Dictionary<string, BackgroundWorker> workers = new Dictionary<string,BackgroundWorker>();

Then, for each worker you need to add the handler for the RunWorkerCompleted event in which you remove the workers from your dictionary:

 background.RunWorkerCompleted += (s, e) => 
            {
                var instance = (BackgroundWorker)s;
                if (e.Cancelled)
                {
                    //cancelled
                }
                else
                {
                    //finished
                }
                workers.Remove(workers.FirstOrDefault(x => x.Value == (instance)).Key);
                //announce worker removed
            };

You add your workers to the dictionary with row identifier as key. When you want to stop a worker you just call

workers[rowId].CancelAsync();

You should also check for the CancellationPending flag in your DoWork method (from here): This property is meant for use by the worker thread, which should periodically check CancellationPending and abort the background operation when it is set to true.

CancelAsync can't actually stop your work for you because it doesn't know what you're doing, so you have to check for the flag yourself and cancel your own work. This also allows you to preform any cleanup operations you might need to before ending the worker. This allows you to check the Cancellation flag of the worker.

EDIT: To avoid your process blocking the worker instead of proc.WaitForExit() use:

while(!proc.HasExited)
{
    if(instance.CancellationPending)
    {
        //killprocess
        break;
    }
}

Upvotes: 1

Related Questions