2009-12-14 29 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