代码之家  ›  专栏  ›  技术社区  ›  thomasb DaveRead

使后台工作人员在不冻结窗体的情况下按顺序执行多个操作

  •  3
  • thomasb DaveRead  · 技术社区  · 15 年前

    我已经问了一个类似的问题 here 但我现在有一个后续问题。

    我需要连续多次启动外部程序,但我有几个问题:

    • 它试图同时启动所有的操作。我放了一个空的“while”(bwksvn.isbusy)”,这有点管用,但我很肯定这会让你们中的一些人哭一点。
    • 只要所有操作都没有完成,它仍然冻结窗体。考虑到其他一些这样的主题,我认为我的代码的编写方式,应用程序不是真正的多线程的,或者我没有利用它…但我真的不熟悉穿线。
    • 它似乎不像我要求它做的那样。我将尝试更简单的操作,看看操作是否失败,或者后台工作人员是否从未启动。

    这是密码(对不起,有点长):

    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 回复  |  直到 15 年前
        1
  •  5
  •   Hans Passant    15 年前

    解决方案很简单:执行一个bgw 全部的 命令,而不是每个命令一个bgw。你需要一个 List<svnCommand> 存储命令,以便轻松地将它们传递给runworkerasync()。doWork()可以简单地用foreach迭代列表。

        2
  •  4
  •   Henk Holterman    15 年前

    这个 while (bgwkSVN.IsBusy) { } 在一个紧密的循环中等待,看起来这会导致你的延迟。我将把这个进程分成几个后台线程,然后在backgroundworkerx_runworkercompleted中启动“下一个”。

        3
  •  4
  •   Oliver    15 年前

    所以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
  •  2
  •   Cory Charlton    15 年前

    事实上你有一个 while (bgwkSVN.IsBusy) { } 在主窗体线程中,是窗体停止响应的原因。后台工作线程正在单独的线程上执行它的工作,但您的UI线程被阻止。您应该考虑在 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();
    }