2009-12-14 75 views
3

我已经问过一个类似问题here但我现在有一个后续问题。让BackgroundWorker按顺序执行多个操作,而不冻结表格

我需要多次运行外部程序成一排,但我有几个问题是:

  • 它试图同时启动所有操作。我已经把一个空的“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; 
} 

谢谢!

回答

5

解决方案很简单:让一个BGW执行所有的命令,而不是每个命令只有一个BGW。您需要一个List<svnCommand>来存储这些命令,以便您可以轻松地将它们传递给RunWorkerAsync()。 DoWork()可以简单地用foreach迭代列表。

+0

我做到了,但现在我可以无法访问DoWork方法中的表单控件?而且,由于我只启动一个BackgroundWorker,所以在进程运行时我无法更新输出? – thomasb 2009-12-15 10:25:23

+1

您无法在两者之前访问控件。将任何DoWork需求放在svnCommand类中。它可以更新输出,使用ReportProgress – 2009-12-15 13:13:59

+0

你是对的,我在没有调试的情况下启动程序(用web dev开发的习惯),结果发现后台工作人员在后台默默地崩溃。所以这就是为什么没有真正做到。 – thomasb 2009-12-15 14:49:59

2

事实上,您的主表单线程中有一个while (bgwkSVN.IsBusy) { }是您的表单停止响应的原因。后台工作人员正在执行它在单独的线程上工作,但是您的UI线程被阻止。您应该考虑在MergerRevisions调用中启动一个RunWorkerAsync()方法,然后在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(); 
} 
+0

如果您在GUI调用了Thread.Sleep()线程它不会为及时响应。所以这并没有真正的帮助。 – Oliver 2009-12-15 13:34:38

+0

你是对的,这就是为什么我列出了正确的方法来做到这一点,然后给Thread.Sleep()选项做错误的方法。如果他的BackgroundWorker工作时间超过1秒(或者他传递给Sleep()的任何值),那么我错误的做法会比他最初编码的方式更好:-) – 2009-12-15 16:19:36

4

while (bgwkSVN.IsBusy) { }正在紧张地等待,看起来像是在拖延时间。我将这个过程分成几个后台线程,并在backgroundWorkerX_RunWorkerCompleted中启动'下一个'。

4

所以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..."); 
      } 
     } 
    } 
} 
+0

大声笑我现在认识到这是要走的路,但该死的是很多代码来更新状态标签和进度条。 – Jack 2010-05-20 21:46:05