Заставьте BackgroundWorker выполнять несколько операций последовательно, не замораживая форму - PullRequest
3 голосов
/ 14 декабря 2009

Я уже задавал немного похожий вопрос здесь , но теперь у меня есть дополнительный вопрос.

Мне нужно запустить внешнюю программу несколько раз подряд, но у меня есть несколько проблем с ней:

  • Попытка одновременно запустить все операции. Я поставил пустое «while (bgwkSVN.IsBusy) {}», это вроде работает, но я уверен, что это заставит некоторых из вас немного плакать.
  • Он по-прежнему замораживает форму, пока все операции не завершены. Учитывая несколько других тем SO, я думаю, как написан мой код, приложение на самом деле не многопоточное, или я им не пользуюсь ... но я действительно не знаком с многопоточностью.
  • Кажется, он не делает то, что я прошу. Я попытаюсь выполнить более простую операцию, чтобы выяснить, не удалась ли операция или не был ли запущен фоновый работник.

Вот код (извините, он довольно длинный):

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;
}

Спасибо!

Ответы [ 4 ]

5 голосов
/ 15 декабря 2009

Решение простое: один BGW должен выполнить все команд, а не только один BGW для каждой команды. Вам понадобится List<svnCommand> для хранения команд, чтобы вы могли легко передавать их в RunWorkerAsync (). DoWork () может просто перебрать список с помощью foreach.

4 голосов
/ 15 декабря 2009

Итак, nobugz уже дает правильное направление, но для полноты вот пример кода:

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...");
            }
        }
    }
}
4 голосов
/ 14 декабря 2009

while (bgwkSVN.IsBusy) { } ждет в узком цикле и похоже, что это вызывает ваши задержки. Я бы разделил процесс на несколько фоновых потоков и запустил следующий в backgroundWorkerX_RunWorkerCompleted.

2 голосов
/ 14 декабря 2009

Тот факт, что у вас есть while (bgwkSVN.IsBusy) { } в основной ветке формы, является причиной того, что ваша форма перестает отвечать. Фоновый рабочий выполняет свою работу в отдельном потоке, но ваш поток пользовательского интерфейса заблокирован. Следует рассмотреть возможность запуска одного метода RunWorkerAsync () в вызове MergerRevisions, а затем запустить следующий в событии bgwkSVN.RunWorkerCompleted.

Если вы ищете неприятное быстрое исправление, неправильный способ сделать это здесь:

Изменение:

while (bgwkSVN.IsBusy) { }

Кому:

while (bgwkSVN.IsBusy) 
{
    System.Threading.Thread.Sleep(1000); // Make the current (UI/Form) thread sleep for 1 second
    Application.DoEvents();
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...