1

So right now I have a method that calls a different method three times.

List<BackgroundWorker> bgws = new List<BackgroundWorker>();
AutoResetEvent _workerCompleted = new AutoResetEvent(false);

void methodA () {
    methodB(); //bw1
    methodB(); //bw2
    methodB(); //bw3

    //wait for the background workers that were kicked off above to finish
    _workerCompleted.WaitOne();

    Console.Writeline("hey");
}

void methodB() {
    BackgroundWorker bw = new BackgroundWorker();
    bw.WorkerReportsProgress = true;
    bw.WorkerSupportsCancellation = true;
    bgws.Add(bw);

    bw.DoWork += (sender, DoWorkEventArgs) => { bwWork(sender, DoWorkEventArgs, info); };
    bw.ProgressChanged += (sender, ProgressChangedEventArgs) => { bwProgressChanged(sender, ProgressChangedEventArgs, info); };
    bw.RunWorkerCompleted += (sender, RunWorkerCompletedEventArgs) => { bwCompleted(sender, RunWorkerCompletedEventArgs, info); };

    bw.RunWorkerAsync();

}

The bwWork and bwProgressChanged events shouldn't matter too much. The former is just processing a file, the latter is updating a progress bar and label.

private void bwCompleted(object senderr, 
System.ComponentModel.RunWorkerCompletedEventArgs e, SenderInfo info)
{
    BackgroundWorker bw = (BackgroundWorker)senderr;

    Console.WriteLine(Path.GetFileName(info.path) + " : Finished.");

    bgws.Remove(bw); //remove the bw from the list
    //remove events from bw
    bw.DoWork -= (sender, DoWorkEventArgs) => { bwWork(sender, DoWorkEventArgs, info); };
    bw.ProgressChanged -= (sender, ProgressChangedEventArgs) => { bwProgressChanged(sender, ProgressChangedEventArgs, info); };
    bw.RunWorkerCompleted -= (sender, RunWorkerCompletedEventArgs) => { bwCompleted(sender, RunWorkerCompletedEventArgs, info); };
    //dispose bw
    bw.Dispose();
    Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
    if (bgws.Count == 0) //if there are no more bw's, we're good to go
    {
        MessageBox.Show("Done.");
        bgws.Clear();
        _workerCompleted.Set();
    }

}

So in the end, I want all of the background workers to finish what they're doing before "hey" is printed out. I tried using the AutoResetEvent and ManualResetEvent, yet nothing seemed to happen when they were present. What I mean is that none of the background workers even ran when I included them.

I would invoke a thread, but I'm a little reluctant because I was told background workers were the thing to use when doing background work and updating UI. Is there any way that I can wait on the three background workers so that I can continue on with my program?

Tim
  • 51
  • 1
  • 6
  • Are you having trouble getting background worker to actually execute a task, or are you having trouble syncing with all workers and detecting when all are done? I'm not clear what specifically you're having trouble with. – LB2 Jun 06 '17 at 20:21
  • Sorry about that. So if I include the `AutoResetEvent` part in the program, none of the background workers do anything. If I don't include it, the background workers work great, but the `Console.WriteLine("hey");` is executed way before the workers are finished with what they're doing. I suppose I could put more code in the part where I check if the background worker list is empty, but I have a lot more code to go, and I feel like that'd mess with the flow of the program. I just want the background workers to do their stuff, and then to continue on where that writeline statement is. – Tim Jun 06 '17 at 20:26
  • Clarification: Background workers don't do anything (i.e. `bwWork` never gets executed), or they do their work, but never signal full completion so your code waits forever? – LB2 Jun 06 '17 at 20:31
  • You seem to be doing some unnecessary work in `bwCompleted` - why remove events when you are about to `Dispose` of the object? Why `Clear()` the `bgws` list when you already know it is clear? – NetMage Jun 06 '17 at 20:34
  • Interesting. I'm glad you pointed that out. `bwWork` is the only one of the three events that gets executed when the `AutoResetEvent` parts are int he program. Neither the progress changed nor the completed events ever get executed for some reason. – Tim Jun 06 '17 at 20:35
  • The code that you have to remove the handlers from your BGW events isn't actually removing any events; the handlers you're removing are different than the handlers you're adding. That said, removing the handlers is pointless as the BGW is going away entirely anyway. Additionally, rather than passing the BGW as an `object`, you should pass it as a `BackgroundWorker` so that you don't need to cast it. – Servy Jun 06 '17 at 21:48

3 Answers3

1

I believe the issue is that the BackgroundWorker does DoWork on a separate thread, but uses the thread it is created on to run ProgressChanged and RunWorkerCompleted events so they can update the UI. But if you already have the UI thread tied up in a WaitOne or other spin, you can't execute those events.

In your bwCompleted when bgws.Count == 0 why don't you call a methodA_AfterDone to handle the read of methodA and just have methodA end after bw3?

NetMage
  • 26,163
  • 3
  • 34
  • 55
  • Ahh I suppose that's a way to do it. I really wanted to just continue off where I launched the bw's initially, but if I can't then that's fine, too. – Tim Jun 06 '17 at 20:40
  • @Tim Alternatively, you can do both: keep your background worker, but create as many TaskCompletionSource, and transition them to completed in the RunWorkerCompleted event. From there, you can await a Task.WhenAll on your list of tasks, and continue from where you started them – Kevin Gosse Jun 06 '17 at 21:46
  • 2
    @KevinGosse I mean you *could*, but why would you bother re-creating your own less efficient `Task.Run` implementation when you could just use `Task.Run`. – Servy Jun 06 '17 at 21:49
  • @Servy I've just done a search and learned about `Progress`, I didn't know the TPL had a way to report progress. In that light, indeed, there's no point in sticking with BackgroundWorker – Kevin Gosse Jun 06 '17 at 21:51
1

Know I'm two years late but this is what I did in a similar situation. Created a completed counter. Added one to the completed counter each time a BGW from the list finished. Had a seperate MonitorBGW setup with a while loop kinda like what follows.

While(CompletedCounter < ListBGWs.Count){Thead.Sleep(2000)}
//Put my code I wanted run after they all completed here.

Hope this helps someone :)

Md.Sukel Ali
  • 2,987
  • 5
  • 22
  • 34
-1

Simple you don't use BackgroundWorker and switch to Tasks. Task.Run(() => Your work ); And after you do that you can use Task.WaitAll(task1,task2); You can switch any EAP to Tasks. And with async you can make this with ease.

Filip Cordas
  • 2,531
  • 1
  • 12
  • 23
  • Tasks do not guarantee that the task runs on a different thread - it may run on the same, so it may tie up the UI thread anyway. – LB2 Jun 06 '17 at 21:02
  • Tasks don't but 'Task.Run' will run on a different thread and will not block the UI. And as I said you can wrap BackgroundWorker With a Task so he can use Task.WaitAll or Task WhenAll – Filip Cordas Jun 06 '17 at 21:19
  • Will Tasks guarantee the progress event runs on the UI thread? – NetMage Jun 06 '17 at 21:40
  • @NetMage There are tools in the TPL for doing so, yes. – Servy Jun 06 '17 at 21:45
  • @NetMage TPL is the recommended way of doing long running background operations for years. For progress you can have a look at [this](https://stackoverflow.com/a/40600451/6330636) – Filip Cordas Jun 06 '17 at 22:46