Reputation: 2670
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
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
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