0

I am having a bit of a conundrum here, and would like to know a couple of things:

  1. Am i doing this wrong?
  2. What is the expected behaviour of a backgroundworker in different scenarios...
  3. If possible, get an answer as to why i am getting specific behaviour would be nice...

For point 1, and ultimately 3 as well, i will explain what i am doing in Pseudo-Code so that you have the details without actually spitting out thousands of lines of code. While i write this post, i will look at the code itself to ensure that the information is accurate as far as when and what is happening. At the very end, i will also detail what is happening and why i am having issues.

Pseudo-Code details:

I have a main UI thread (WinForms form), where after selecting a few configuration options you click a button.

This button's event does some preliminary setup work in memory and on the file system to get things going and once that's done fires off ONE backgroundworker. This backgroundworker initializes 5 other backgroundworkers (form scope variables), sets their "Done" flags (bool - same scope) to true, sets their "Log" vars to a new List<LogEntry> (same scope) and once that's done calls a method called CheckEndConditions. This method call is done within the DoWork() of the initial backgroundworker, and not in the RunWorkerCompleted event.

The CheckEndConditions method does the following logic:

  1. IF ALL "Done" vars are set to True...
  2. Grab the "Log" vars for all 5 BWs and adds their content to a master log.
  3. Reset the "Log" vars for all 5 BWs to a new List<LogEntry>
  4. Reset the "Done" vars for all 5 BWs to False.
  5. Call MoveToNextStep() method which returns an Enum value representative of the next step to perform
  6. Based on the result of (5), grab a List<ActionFileAction> that needs to be processed
  7. Check to ensure (6) has actions to perform
  8. If NO, set ALL "Done" flags to true, and call itself to move to the next step...
  9. If YES, partition this list of actions into 5 lists and place them in an array of List<ActionFileAction> called ThreadActionSets[]
  10. Check EACH partitioned list for content, and if none, sets the "Done" flag for the respective thread to true (this ensures there are no "end race scenarios")
  11. Fire off all 5 threads using RunWorkerAsync() (unless we are at the Finished step of course)
  12. Return

Each BW has the exact same DoWork() code, which basically boils down to the following:

  1. Do i have any actions to perform?
  2. If NO, set my e.Result var to an empty list of log entries and exit.
  3. If YES, loop for each action in the set and perform 4-5-6 below...
  4. What context of action am i doing? (Groups, Modules, etc)
  5. Based on (4), what type of action am i doing? (Add, Delete, Modify)
  6. Based on (5), perform the right action and log everything you do locally
  7. When all actions are done, set my e.Result var to the "log of everything i've done", and exit.

Each BW has the same RunWorkerCompleted() code, which basically boils down to the following:

TRY

  1. From the e.Result var, grab the List<LogEntry> and put it in my respective thread's "Log" var.
  2. Set my respective "Done" var to true
  3. Call CheckEndConditions()

CATCH

  1. Set my respective "Done" var to true
  2. Call CheckEndConditions()

So that is basically it... in summary, i am splitting a huge amount of actions into 5 partitions, and sending those off to 5 threads to perform them at a faster rate than on a single thread.

The Problem

The problem i am having is that i often find myself, regardless of how much thought i put into this for race scenarios (specifically end ones), with a jammed/non-responsive program.

In the beginning, i had setup my code inefficiently and the problem was with End Race Scenarios and the threads would complete so fast that the last call made to CheckEndConditions saw one of the "Done" vars still set to false, when in fact it wasn't/it had completed... So i changed my code to what you see above which, i thought, would fix the problem, but it hasn't. The whole process still jams/falls asleep, and no threads are actually running any processing when this happens which means that something went wrong (i think, not sure) with the last call to CheckEndConditions.

So my 1st question: Am i doing this wrong? What is the standard way of doing what it is i want to do? The logic of what i've done feels sound to me, but it doesn't behave how i expect it to so maybe the logic isn't sound? ...

2nd question: What is the expected behaviour of a BW, when this scenario occurs: An error occurred within the DoWork() method that was un-caught... does it fire off the RunWorkerCompleted() event? If not, what happens?

3rd question: Does anyone see something obvious as to why my problem is occurring?

Thanks for the help!

MaxOvrdrv
  • 1,780
  • 17
  • 32
  • Where does it hang when debugging? (You can pause the debuggin to exactly see which thread is where) – CSharpie Jul 29 '14 at 17:14
  • that's just it... i have code to update the UI while it's running, so i wait for the UI to stop updating/stuck at one spot for a while, so i pause the debugger, and i get that Done1, 2, 3, 4, 5 are all set to True, but NOTHING is happening... almost like CheckEndConditions wasn't called at a point or something... :( And if i set breakpoints in the RunWorkerCompleted, it never jams... – MaxOvrdrv Jul 29 '14 at 17:17
  • Where are the workerthreads hanging then? Cant you post some code? – CSharpie Jul 29 '14 at 17:23
  • It sounds like your using the "Done" and "Log" variables to share data between threads. Are you using locks to access these variables? This might not be the root of your problem, but its the first thing I would address. – Pat Hensel Jul 29 '14 at 17:30
  • 1
    You need to learn to fear the threading beast, heisenbugs like this are common. A *bool* is not a proper synchronization object and *will* malfunction like you describe. Proper code uses WaitHandle.WaitAll() or Task.WaitAll(). Task ought to be your preference, BackgroundWorker isn't the proper tool to get this job done. – Hans Passant Jul 29 '14 at 17:41
  • actually each var is independant of the others... so each thread has their own "Wait" vars, and the CheckEndConditions is the "WaitAll" method... the RunWorkerCompleted events set these vars FIRST, and THEN calls CheckEndConditions... which, logically, should be sufficient/the same no? – MaxOvrdrv Jul 29 '14 at 17:44
  • @PatHensel so you're saying that because i may be "Setting" a "Done" var (say Done1) and also "Reading" the same var (in the IF statement of the CheckEndConditions method) i could encounter problems, and that locking those when i set them in the RunWorkerCompleted events would help? – MaxOvrdrv Jul 29 '14 at 17:52
  • @CSharpie the workerthreads are not jammed... that's just it... the entire program/code is at a "I am done/I have nothing to do" state... which means it didn't move to the next step for some reason... – MaxOvrdrv Jul 29 '14 at 17:57
  • @MaxOvrdrv I'm saying (like Hans is saying) that you shouldn't use bools without proper locking semantics to share data between threads. – Pat Hensel Jul 29 '14 at 18:10
  • @PatHensel yeah the only thing that you're saying, and that i probably don't understand, is that the objects are not "shared" between threads... unless you mean shared between the Parent and the Child threads? in which case i thought that RunWorkerCompleted was "Safe" because by definition it executes on the Parent thread... ? – MaxOvrdrv Jul 29 '14 at 18:13
  • 1
    @MaxOvrdrv The RunWorkerCompleted event will not necessarily be raised on the same thread that it was created on (unless it is created on UI thread) See http://stackoverflow.com/questions/2806814/backgroundworker-runworkercompleted-event – Pat Hensel Jul 29 '14 at 18:29
  • @PatHensel Please post your comment as an answer and i will accept it. If you look at the pseudo in the original post, i "fire off" my first 5 threads, from a call to CheckEndConditions within another thread's DoWork... which essentially means that they become "parent-less". So i moved the method call into the RunWorkerCompleted event of the starter thread (which was created and launched on the UI thread, so the RunWorker on that one is executed on the UI), and that solved all my problems. No more jamming since now all 5 threads always come back to the UI thread when completed. THANK YOU! :) – MaxOvrdrv Jul 29 '14 at 18:44

1 Answers1

2

Reposting my comment as answer per OP's request:

The RunWorkerCompleted event will not necessarily be raised on the same thread that it was created on (unless it is created on UI thread) See BackgroundWorker RunWorkerCompleted Event

See OP comments for more details.

Community
  • 1
  • 1
Pat Hensel
  • 1,274
  • 9
  • 11
  • Thank you. I was able to move the original "Spawn" calls to the main UI thread, and that eliminated any End Race Scenarios and stopped the jams from occurring. – MaxOvrdrv Jul 29 '14 at 18:59