3

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 :

  • It tried to simultaneously launch all the operations. I have put an empty "while (bgwkSVN.IsBusy) { }", it kinda works, but I'm pretty sure it will make some of you cry a little.
  • It still freezes the form as long as all the operations are not completed. Given a few other SO topics, I think the way my code is written, the application isn't really multithreaded, or I don't take advantage of it... but I'm really not familiar with threading.
  • It doesn't seems to do what I ask it to do. I shall try with a more simple operation, to see if the operation doesn't succeeds, or if the backgroundworker is never launched.

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 !

Community
  • 1
  • 1
thomasb
  • 5,816
  • 10
  • 57
  • 92

4 Answers4

5

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.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • I did that, but now I can't access the form's controls in the DoWork method ? And, given that I only launch one BackgroundWorker, I can't update the output while the process is runing ? – thomasb Dec 15 '09 at 10:25
  • 1
    You couldn't access control before either. Put whatever DoWork needs in the svnCommand class. It can update the output, use ReportProgress – Hans Passant Dec 15 '09 at 13:13
  • You're right, I was starting the program without debugging (habit taken with web dev), and it turns out the background worker was silently crashing in the background. So that's why nothing was actually done. – thomasb Dec 15 '09 at 14:49
  • Use the RunWorkerCompletedEventArgs.Error property. Throw it if it isn't null. – Hans Passant Dec 15 '09 at 15:27
5

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...");
            }
        }
    }
}
Oliver
  • 43,366
  • 8
  • 94
  • 151
  • Lol I now realize this is the way to go, but damn that's a lot of code to update a status label and a progress bar. – Jack May 20 '10 at 21:46
4

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.

H H
  • 263,252
  • 30
  • 330
  • 514
tzerb
  • 1,433
  • 1
  • 16
  • 35
2

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();
}
Cory Charlton
  • 8,868
  • 4
  • 48
  • 68
  • If you call Thread.Sleep() on your GUI Thread it won't respond for that time. So this doesn't really help. – Oliver Dec 15 '09 at 13:34
  • You are correct which is why I outlined the correct way to do it and then gave the Thread.Sleep() option as the wrong way to do it. If his BackgroundWorker was taking more than 1 second (or whatever value he passed to Sleep()) then my wrong way to do it would be better than the way he originally had it coded :-) – Cory Charlton Dec 15 '09 at 16:19