使BackgroundWorker按顺序执行多个操作,而不冻结表单
我已经在这里问了一个类似的问题,但我现在有一个后续问题。
我需要连续几次启动外部程序,但我有几个问题:
- 它试图同时启动所有操作。 我放了一个空的“while(bgwkSVN.IsBusy){}”,它有点工作,但我很确定它会让你们中的一些人哭一点。
- 只要所有操作都没有完成,它仍会冻结表单。 鉴于其他一些SO主题,我认为我的代码编写方式,应用程序并不是真正的multithreading,或者我没有利用它…但我真的不熟悉线程。
- 它似乎没有做我要求它做的事情。 我将尝试更简单的操作,以查看操作是否成功,或者后台工作程序是否从未启动。
这是代码(抱歉,它有点长):
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); } /// /// Set up the BackgroundWorker object by attaching event handlers /// 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); } /// /// Exécuter une commande SVN /// 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; } /// /// Where the actual, potentially time-consuming work is done. /// 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); } /// /// Deals with the results of the background operation /// 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(); } } /// /// Updates the progress bar /// private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e) { this.progressBarTraitement.Value += e.ProgressPercentage; }
谢谢 !
解决方案很简单:让一个BGW执行所有命令,而不是每个命令只执行一个BGW。 您需要一个List
来存储命令,以便您可以轻松地将它们传递给RunWorkerAsync()。 DoWork()可以简单地使用foreach迭代列表。
while (bgwkSVN.IsBusy) { }
在紧急循环中等待,看起来它会导致您的延迟。 我将进程拆分为几个后台线程,并在backgroundWorkerX_RunWorkerCompleted中启动’next’。
所以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> _Commands; private Random _Random; public FormMain() { InitializeComponent(); _Random = new Random(); _Commands = new Queue>(); _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 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 (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..."); } } } }
您在主表单线程中有一段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(); }