如何正确等待,直到BackgroundWorker完成?
观察下面的一段代码:
var handler = GetTheRightHandler(); var bw = new BackgroundWorker(); bw.RunWorkerCompleted += OnAsyncOperationCompleted; bw.DoWork += OnDoWorkLoadChildren; bw.RunWorkerAsync(handler);
现在假设我想等到bw
完成工作。 什么是正确的方法呢?
我的解决办法是:
bool finished = false; var handler = GetTheRightHandler(); var bw = new BackgroundWorker(); bw.RunWorkerCompleted += (sender, args) => { OnAsyncOperationCompleted(sender, args); finished = true; }); bw.DoWork += OnDoWorkLoadChildren; bw.RunWorkerAsync(handler); int timeout = N; while (!finished && timeout > 0) { Thread.Sleep(1000); --timeout; } if (!finished) { throw new TimedoutException("bla bla bla"); }
但我不喜欢它。
我已经考虑用同步事件replacefinished
标志,将其设置在RunWorkerCompleted
处理程序中,稍后将其阻止,而不是执行while-sleep循环。
唉,这是错误的,因为代码可能运行在WPF或WindowsForm同步上下文,在这种情况下,我会阻止相同的线程作为RunWorkerCompleted
处理程序运行,这显然不是很聪明的举动。
我想知道更好的解决scheme。
谢谢。
编辑:
PS
- 示例代码是故意澄清我的问题。 我完全知道完成callback,但我想知道如何等待直到完成。 这是我的问题。
- 我知道的
Thread.Join
,Delegate.BeginInvoke
,ThreadPool.QueueUserWorkItem
,等等…问题是具体关于BackgroundWorker
。
编辑2:
好吧,我想如果我解释一下这个场景,会更容易些。
我有一个unit testing方法,该方法调用一些asynchronous代码,最终调用一个BackgroundWorker
,我可以传递一个完成处理程序。 所有的代码是我的,所以我可以改变实现,如果我想。 但是,我不打算replaceBackgroundWorker
,因为它会自动使用正确的同步上下文,以便在UI线程上调用代码时,在同一个UI线程上调用完成callback,这非常好。
无论如何,unit testing方法有可能在BW完成工作之前就结束了,这并不好。 所以我希望等到BW完成,想知道最好的方法。
还有更多的东西,但是整体画面或多或less像我刚才所描述的那样。
尝试使用这样的AutoResetEvent类:
var doneEvent = new AutoResetEvent(false); var bw = new BackgroundWorker(); bw.DoWork += (sender, e) => { try { if (!e.Cancel) { // Do work } } finally { doneEvent.Set(); } }; bw.RunWorkerAsync(); doneEvent.WaitOne();
警告:无论发生什么情况, doneEvent.Set()
应该确保doneEvent.Set()
被调用。 你也可能想要提供一个指定超时期限的参数doneEvent.WaitOne()
。
注意:这个代码几乎是Fredrik Kalseth对类似问题的一个副本。
要等待后台工作线程(单个或多个),请执行以下操作:
-
创build您已经编程创build的后台工作者列表:
private IList<BackgroundWorker> m_WorkersWithData = new List<BackgroundWorker>();
-
在列表中添加后台工作人员:
BackgroundWorker worker = new BackgroundWorker(); worker.DoWork += new DoWorkEventHandler(worker_DoWork); worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged); worker.WorkerReportsProgress = true; m_WorkersWithData.Add(worker); worker.RunWorkerAsync();
-
使用以下函数等待列表中的所有工作人员:
private void CheckAllThreadsHaveFinishedWorking() { bool hasAllThreadsFinished = false; while (!hasAllThreadsFinished) { hasAllThreadsFinished = (from worker in m_WorkersWithData where worker.IsBusy select worker).ToList().Count == 0; Application.DoEvents(); //This call is very important if you want to have a progress bar and want to update it //from the Progress event of the background worker. Thread.Sleep(1000); //This call waits if the loop continues making sure that the CPU time gets freed before //re-checking. } m_WorkersWithData.Clear(); //After the loop exits clear the list of all background workers to release memory. //On the contrary you can also dispose your background workers. }
BackgroundWorker有一个完成事件。 不要等待,从完成处理程序中调用剩余的代码path。
这个问题很老,但我不认为作者得到了他正在寻找的答案。
这有点肮脏,它在VB.NET,但为我工作
Private Sub MultiTaskingForThePoor() Try 'Start background worker bgwAsyncTasks.RunWorkerAsync() 'Do some other stuff here For i as integer = 0 to 100 lblOutput.Text = cstr(i) Next 'Wait for Background worker While bgwAsyncTasks.isBusy() Windows.Forms.Application.DoEvents() End While 'Voila, we are back in sync lblOutput.Text = "Success!" Catch ex As Exception MsgBox("Oops!" & vbcrlf & ex.Message) End Try End Sub
VB.NET
While BackgroundWorker1.IsBusy() Windows.Forms.Application.DoEvents() End While
你可以用它来链接多个事件。 (sudo代码遵循)
download_file("filepath") While BackgroundWorker1.IsBusy() Windows.Forms.Application.DoEvents() End While 'Waits to install until the download is complete and lets other UI events function install_file("filepath") While BackgroundWorker1.IsBusy() Windows.Forms.Application.DoEvents() End While 'Waits for the install to complete before presenting the message box msgbox("File Installed")
在Application.DoEvents()
循环中检查backgrWorker.IsBusy
不是一个好方法。
我同意@JohannesH,你应该明确地使用AutoResetEvent作为一个优雅的解决scheme。 但不会在UI线程中使用它,会导致主线程被阻塞; 它应该来自另一个后台工作线程。
AutoResetEvent aevent = new AutoResetEvent(false); private void button1_Click(object sender, EventArgs e) { bws = new BackgroundWorker(); bws.DoWork += new DoWorkEventHandler(bw_work); bws.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_complete); bws.RunWorkerAsync(); bwWaiting.DoWork += new DoWorkEventHandler(waiting_work); bwWaiting.RunWorkerCompleted += new RunWorkerCompletedEventHandler(waiting_complete); bwWaiting.RunWorkerAsync(); } void bw_work(object sender, DoWorkEventArgs e) { Thread.Sleep(2000); } void bw_complete(object sender, RunWorkerCompletedEventArgs e) { Debug.WriteLine("complete " + bwThread.ToString()); aevent.Set(); } void waiting_work(object sender, DoWorkEventArgs e) { aevent.WaitOne(); } void waiting_complete(object sender, RunWorkerCompletedEventArgs e) { Debug.WriteLine("complete waiting thread"); }
不太清楚你在等什么。 你的意思是说你想要完成某件事情之后(BW)完成了一些事情吗? 像你一样使用bw.RunWorkerCompleted(使用一个单独的函数来提高可读性),并且在这个callback函数中你会做下一个东西。 启动一个计时器来检查工作是否需要很长时间。
var handler = GetTheRightHandler(); var bw = new BackgroundWorker(); bw.RunWorkerCompleted += (sender, args) => { OnAsyncOperationCompleted(sender, args); }); bw.DoWork += OnDoWorkLoadChildren; bw.RunWorkerAsync(handler); Timer Clock=new Timer(); Clock.Interval=1000; Clock.Start(); Clock.Tick+=new EventHandler(Timer_Tick); public void Timer_Tick(object sender,EventArgs eArgs) { if (bw.WorkerSupportsCancellation == true) { bw.CancelAsync(); } throw new TimedoutException("bla bla bla"); }
在OnDoWorkLoadChildren中:
if ((worker.CancellationPending == true)) { e.Cancel = true; //return or something }
在OpenCV中存在函数WaitKey。 Ir允许以这种方式解决这个问题:
while (this->backgroundWorker1->IsBusy) { waitKey(10); std::cout << "Wait for background process: " << std::endl; } this->backgroundWorker1->RunWorkerAsync();
我也在寻找一个合适的解决scheme。 我用排他锁解决了等待。 代码中的关键path是写入公共容器(这里是控制台),增加或减less工作人员。 在写入这个variables时,没有线程会干扰,否则计数不再保证。
public class Program { public static int worker = 0; public static object lockObject = 0; static void Main(string[] args) { BackgroundworkerTest backgroundworkerTest = new BackgroundworkerTest(); backgroundworkerTest.WalkDir("C:\\"); while (backgroundworkerTest.Worker > 0) { // Exclusive write on console lock (backgroundworkerTest.ExclusiveLock) { Console.CursorTop = 4; Console.CursorLeft = 1; var consoleOut = string.Format("Worker busy count={0}", backgroundworkerTest.Worker); Console.Write("{0}{1}", consoleOut, new string(' ', Console.WindowWidth-consoleOut.Length)); } } } } public class BackgroundworkerTest { private int worker = 0; public object ExclusiveLock = 0; public int Worker { get { return this.worker; } } public void WalkDir(string dir) { // Exclusive write on console lock (this.ExclusiveLock) { Console.CursorTop = 1; Console.CursorLeft = 1; var consoleOut = string.Format("Directory={0}", dir); Console.Write("{0}{1}", consoleOut, new string(' ', Console.WindowWidth*3 - consoleOut.Length)); } var currentDir = new System.IO.DirectoryInfo(dir); DirectoryInfo[] directoryList = null; try { directoryList = currentDir.GetDirectories(); } catch (UnauthorizedAccessException unauthorizedAccessException) { // No access to this directory, so let's leave return; } foreach (var directoryInfo in directoryList) { var bw = new BackgroundWorker(); bw.RunWorkerCompleted += (sender, args) => { // Make sure that this worker variable is not messed up lock (this.ExclusiveLock) { worker--; } }; DirectoryInfo info = directoryInfo; bw.DoWork += (sender, args) => this.WalkDir(info.FullName); lock (this.ExclusiveLock) { // Make sure that this worker variable is not messed up worker++; } bw.RunWorkerAsync(); } } }
我使用了BackgroundWorker的 任务
您可以创build任意数量的任务并将其添加到任务列表中。 添加任务时,工作人员将启动,如果在工作人员IsBusy时添加了任务,则重新启动,一旦没有更多任务,则停止。
这将允许您在不冻结的情况下尽可能多地更新GUI。
这适用于我。
// 'tasks' is simply List<Task> that includes events for adding objects private ObservableCollection<Task> tasks = new ObservableCollection<Task>(); // this will asynchronously iterate through the list of tasks private BackgroundWorker task_worker = new BackgroundWorker(); public Form1() { InitializeComponent(); // set up the event handlers tasks.CollectionChanged += tasks_CollectionChanged; task_worker.DoWork += task_worker_DoWork; task_worker.RunWorkerCompleted += task_worker_RunWorkerCompleted; task_worker.WorkerSupportsCancellation = true; } // ----------- worker events void task_worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { if (tasks.Count != 0) { task_worker.RunWorkerAsync(); } } void task_worker_DoWork(object sender, DoWorkEventArgs e) { try { foreach (Task t in tasks) { t.RunSynchronously(); tasks.Remove(t); } } catch { task_worker.CancelAsync(); } } // ------------- task event // runs when a task is added to the list void tasks_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { if (!task_worker.IsBusy) { task_worker.RunWorkerAsync(); } }
现在,您只需要创build一个新的Task并将其添加到List <>中。 它将按照放置在List <>中的顺序由工作人员运行
Task t = new Task(() => { // do something here }); tasks.Add(t);