Reputation: 6037
I have already asked a somwhat similar question here but I now have a follow-up question.
I need to launch the external program several times in a row, but I have several problems with it :
Here is the code (sorry, it's kinda long) :
private struct svnCommand
{
public svnCommand(string args, string path, int pourcent)
{
this.args = args;
this.path = path;
this.pourcent = pourcent;
}
public string args;
public string path;
public int pourcent;
}
private BackgroundWorker bgwkSVN;
public Merger()
{
InitializeComponent();
InitializeBackgroundWorker();
this.textBoxCheminRacine.Text = cheminRacine;
}
private void MergerRevisions(object sender, EventArgs e)
{
activerControles(false);
textBoxOutput.Text = "";
cheminRacine = textBoxCheminRacine.Text;
if (!cheminRacine.EndsWith("\\")) { cheminRacine = cheminRacine + "\\"; }
string branchToMerge = this.textBoxBranche.Text;
if (branchToMerge.StartsWith("/")) { branchToMerge = branchToMerge.Substring(1); }
// révision(s)
string revisions = "";
foreach (string r in textBoxRevision.Text.Split(','))
{
int rev;
if (int.TryParse(r, out rev))
{
revisions += string.Format(" -r {0}:{1}", rev - 1, rev);
}
else
{
revisions += " -r " + r.Replace("-", ":");
}
}
// pourcentage de complétion pour chaque étape
int stepPourcent = (int)Math.Floor((double)(100 / (3 + Directory.GetDirectories(cheminRacine + "branches").Length)));
// merge sur le trunk
while (bgwkSVN.IsBusy) { }
bgwkSVN.RunWorkerAsync(new svnCommand(string.Format("merge --accept postpone {0} {1}{2} .", revisions, svnbasepath, branchToMerge), cheminRacine + "trunk", stepPourcent));
// merge sur chaque branche
string[] branches = Directory.GetDirectories(cheminRacine + "branches");
foreach (string b in branches)
{
while (bgwkSVN.IsBusy) { }
bgwkSVN.RunWorkerAsync(new svnCommand(string.Format("merge --accept postpone {0} {1}{2} .", revisions, svnbasepath, branchToMerge), b, stepPourcent));
}
// virer les mergeinfo
while (bgwkSVN.IsBusy) { }
bgwkSVN.RunWorkerAsync(new svnCommand("pd svn:mergeinfo . -R", cheminRacine, stepPourcent));
// svn update
while (bgwkSVN.IsBusy) { }
bgwkSVN.RunWorkerAsync(new svnCommand("update", cheminRacine, stepPourcent));
textBoxOutput.Text += Environment.NewLine + "Terminé.";
MessageBox.Show("Merge terminé.", "Merge terminé", MessageBoxButtons.OK);
// réactiver les champs et boutons
activerControles(true);
}
/// <summary>
/// Set up the BackgroundWorker object by attaching event handlers
/// </summary>
private void InitializeBackgroundWorker()
{
bgwkSVN = new BackgroundWorker();
bgwkSVN.WorkerReportsProgress = true;
bgwkSVN.WorkerSupportsCancellation = true;
bgwkSVN.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
bgwkSVN.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker1_RunWorkerCompleted);
bgwkSVN.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker1_ProgressChanged);
}
/// <summary>
/// Exécuter une commande SVN
/// </summary>
private string SVNcmd(svnCommand s, BackgroundWorker worker, DoWorkEventArgs e)
{
string o = "";
o += s.path + Environment.NewLine + s.args + Environment.NewLine;
if (worker.CancellationPending)
{
e.Cancel = true;
}
else
{
Process p = new Process();
p.StartInfo.WorkingDirectory = s.path;
p.StartInfo.FileName = "svn";
p.StartInfo.Arguments = s.args;
p.StartInfo.CreateNoWindow = true;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.UseShellExecute = false;
p.Start();
o += p.StandardOutput.ReadToEnd() + Environment.NewLine;
p.WaitForExit();
if (s.pourcent > 0)
{
worker.ReportProgress(s.pourcent);
}
}
return o;
}
/// <summary>
/// Where the actual, potentially time-consuming work is done.
/// </summary>
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
// Get the BackgroundWorker that raised this event.
BackgroundWorker worker = sender as BackgroundWorker;
// Assign the result of the computation to the Result property of the DoWorkEventArgs
// object. This is will be available to the RunWorkerCompleted eventhandler.
e.Result = SVNcmd((svnCommand)e.Argument, worker, e);
}
/// <summary>
/// Deals with the results of the background operation
/// </summary>
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// First, handle the case where an exception was thrown.
if (e.Error != null)
{
MessageBox.Show(e.Error.Message);
}
else if (e.Cancelled)
{
textBoxOutput.Text += Environment.NewLine + "Annulé.";
}
else
{
textBoxOutput.Text += e.Result.ToString();
}
}
/// <summary>
/// Updates the progress bar
/// </summary>
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.progressBarTraitement.Value += e.ProgressPercentage;
}
Thanks !
Upvotes: 3
Views: 6801
Reputation: 45071
So nobugz give you already the right direction, but for completeness here is some sample code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;
namespace Threading
{
public partial class FormMain : Form
{
private BackgroundWorker _BackgroundWorker;
private Queue<Func<string>> _Commands;
private Random _Random;
public FormMain()
{
InitializeComponent();
_Random = new Random();
_Commands = new Queue<Func<string>>();
_BackgroundWorker = new BackgroundWorker();
_BackgroundWorker.WorkerReportsProgress = true;
_BackgroundWorker.WorkerSupportsCancellation = true;
_BackgroundWorker.DoWork += backgroundWorker_DoWork;
_BackgroundWorker.ProgressChanged += backgroundWorker_ProgressChanged;
_BackgroundWorker.RunWorkerCompleted += backgroundWorker_RunWorkerCompleted;
_BackgroundWorker.RunWorkerAsync();
}
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
while (!_BackgroundWorker.CancellationPending)
{
if (_Commands.Count > 0)
{
AddMessage("Starting waiting job...");
AddMessage(_Commands.Dequeue().Invoke());
}
Thread.Sleep(1);
}
}
void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar.Value = e.ProgressPercentage;
}
private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
AddMessage("BackgroundWorker doesn't make any further jobs.");
}
private void buttonStart_Click(object sender, EventArgs e)
{
_Commands.Enqueue(DoSomething);
//or maybe with a lambda
//_Commands.Enqueue(new Func<string>(() =>
//{
// string message;
// message = DoSomething();
// return message;
//}));
}
private string DoSomething()
{
int max = 10;
for (int i = 1; i <= max; i++)
{
Thread.Sleep(_Random.Next(10, 1000));
if (_BackgroundWorker.CancellationPending)
{
return "Job aborted!";
}
AddMessage(String.Format("Currently working on item {0} of {1}", i, max));
_BackgroundWorker.ReportProgress((i*100)/max);
}
return "Job is done.";
}
private void AddMessage(string message)
{
if (textBoxOutput.InvokeRequired)
{
textBoxOutput.BeginInvoke(new Action<string>(AddMessageInternal), message);
}
else
{
AddMessageInternal(message);
}
}
private void AddMessageInternal(string message)
{
textBoxOutput.AppendText(String.Format("{0:G} {1}{2}", DateTime.Now, message, Environment.NewLine));
textBoxOutput.SelectionStart = textBoxOutput.Text.Length;
textBoxOutput.ScrollToCaret();
}
private void FormMain_FormClosing(object sender, FormClosingEventArgs e)
{
if (_BackgroundWorker.IsBusy)
{
_BackgroundWorker.CancelAsync();
e.Cancel = true;
AddMessage("Please close only if all jobs are done...");
}
}
}
}
Upvotes: 5
Reputation: 941227
The solution is simple: have one BGW execute all of the commands, not just one BGW for each command. You'll need a List<svnCommand>
to store the commands so you can easily pass them to RunWorkerAsync(). DoWork() can simply iterate the list with foreach.
Upvotes: 5
Reputation: 1433
the while (bgwkSVN.IsBusy) { }
is waiting in a tight loop and looks like it's causing your delays. I'd split the process into several background threads and start the 'next' one in backgroundWorkerX_RunWorkerCompleted.
Upvotes: 4
Reputation: 8938
The fact that you have a while (bgwkSVN.IsBusy) { }
in your main form thread is why your form stops responding. The background worker is executing it's work on a separate thread but your UI thread is blocked. You should consider starting one RunWorkerAsync() method in the MergerRevisions
call and then start the next in the bgwkSVN.RunWorkerCompleted
event.
If you're looking for a nasty quick fix that is the wrong way to do it here it is:
Change:
while (bgwkSVN.IsBusy) { }
To:
while (bgwkSVN.IsBusy)
{
System.Threading.Thread.Sleep(1000); // Make the current (UI/Form) thread sleep for 1 second
Application.DoEvents();
}
Upvotes: 2