none
Can a BackgroundWorker's progress event sometimes execute AFTER the completed event handler is called? RRS feed

  • Question

  • I'm using BackgroungWorker and am intermittently seeing something that looks funky to me. It appears that my progress event handler code sometimes is called after the worker completed event handler is called.  

    At first I thought this must be a design flaw in my code (and it still might be!) but this morning I read the following on MS website:

    "The call to the ReportProgress method is asynchronous and returns immediately."

    What this tells me is that when the worker calls ReportProgress, there's a small time "break" between that call and the calling of the progress event handling code (which occurs in the thread that started the background worker).  

    If this is true, then it seems possible to me that the progress event handling code could execute after the worker code has exited.

    IE, say the worker code calls ReportProgress, then immediately the worker code exits, causing the "worker completed" event to fire. Since its call to ReportProgress was asynch, that means that the progress event code may not still have been called/completed yet. 

    Am I interpreting this correctly? Might a person's progress event handler, on rare occasions, execute after the person's worker completed event handler, or is there something in place to make sure things don't happen this way?

    Thanks for any insight.

    Michael



    Friday, May 18, 2012 4:04 PM

Answers

  • Your option 1 would be the simplest - if you had a "hidden" form (that didn't display), you could still have a Sync context installed, and use that as your "main" thread... Since you started as a GUI application, it would probably "just work".

    The Nito library would provide a simple way to install your own SynchronizationContext, which again, would make things just work.

    Otherwise, you're probably going to have to rework your code.  Frankly, if you're not using a GUI application, BackgroundWorker is (IMO) more trouble than its worth - there are nicer alternatives, especially with all of the new TPL constructs and cancellation options in place.  Most of what is done with a BW can easily be done via Tasks with continuations (see http://reedcopsey.com/2010/04/19/parallelism-in-net-part-17-think-continuations-not-callbacks/) in a way that really "flows" more logically, and ties into the .NET 4 cancellation model (http://msdn.microsoft.com/en-us/library/dd997364.aspx) well.


    Reed Copsey, Jr. - http://reedcopsey.com
    If a post answers your question, please click "Mark As Answer" on that post and "Mark as Helpful".

    • Marked as answer by Veloz Monday, May 21, 2012 3:59 PM
    Friday, May 18, 2012 9:20 PM
    Moderator

All replies

  • "Can a BackgroundWorker's progress event sometimes execute AFTER the completed event handler is called."  Certainly.  Study windows messaging.  You post a message to the message queue and return.  You have to consider that any data you post should never be accessed by the DoEvent code again.
    • Edited by JohnWein Friday, May 18, 2012 4:39 PM
    Friday, May 18, 2012 4:38 PM
  • "Can a BackgroundWorker's progress event sometimes execute AFTER the completed event handler is called."

    No - it actually can't.

    It CAN be called after the background worker's DoWork has finished, but it will get called before the completion event runs. This is because both get posted to the Windows message queue, which will execute in order.  This does mean that the report progress event may run after the DoWork has completed, but the completion event will happen afterwards still.



    Reed Copsey, Jr. - http://reedcopsey.com
    If a post answers your question, please click "Mark As Answer" on that post and "Mark as Helpful".

    Friday, May 18, 2012 6:23 PM
    Moderator
  • Thanks for that - it makes sense.

    However, the plot thickens a bit for me.. there's still some other little detail I'm not seeing. 

    In my progress event handler, imagine I have these lines

    Console.Write("A");
    Console.Write("B");
    Console.Write("C");

    I'm not always seeing A, B, C on the console. sometimes I'm seeing A, C, B,  or maybe just B, C, and this has me really confused.

    My understanding is that the progress event handler is running in the main thread (the thread the started the background worker).. how is it possible that the WriteLine order is getting messed up, and sometimes dropped?  .. it's almost like my progress handler is getting re-entered....??

    Michael


    Friday, May 18, 2012 6:44 PM

  • My understanding is that the progress event handler is running in the main thread (the thread the started the background worker).. how is it possible that the WriteLine order is getting messed up, and sometimes dropped?  .. it's almost like my progress handler is getting re-entered....??


    This isn't entirely true - it's run in the SynchronizationContext that starts the BackgroundWorker's work event.  Are you running this in a Console Application?  If so, there is no SyncrhonizationContext installed, and the events end up just getting run on a threadpool thread, which doesn't guarantee order.

    Try running your test in a Windows Forms or WPF application - you'll see it prints ABC in the proper order, since there will be a proper SynchronizationContext in place.  BW was really intended for use in a GUI application, which is why it works this way.


    Reed Copsey, Jr. - http://reedcopsey.com
    If a post answers your question, please click "Mark As Answer" on that post and "Mark as Helpful".

    Friday, May 18, 2012 6:54 PM
    Moderator
  • It would seem that the OP is using the BGW without a synchronization context, so there is no posting to a message queue.  Anything can happen in any order with threading without synchronization.  With a synchronization context, the events occur in sequence because the ReportProgress posts before Completed.

    Friday, May 18, 2012 7:14 PM
  • All very interesting. Yes, my current use is via a console app.  I've used this exact same implementation of Background Worker before in a GUI app with no such peculiarities.

    So is there a background worker - like solution for console apps, or are you saying I need to work in a Synchronization Context?

    I have a very "typical" need in that I have some "calculator" code that can run for hours and days and yet I still need to monitor for user/io (ie in a main thread). 

    Michael

    Friday, May 18, 2012 7:35 PM
  • I suppose I could rewrite my calculation logic to run in the main thread, and to have it "poll" for user I/O, and thus avoid threading all together, but that's not a tidy/encapsulated solution. It would be best if my calculation code were ignorant of things like polling for user/io..

    M

    Friday, May 18, 2012 7:42 PM
  • Nothing has to be visible, you just need a message pump.  Without a SynchronizationContext, you have to program the synchronization with locks and waits.
    Friday, May 18, 2012 7:42 PM
  • All very interesting. Yes, my current use is via a console app.  I've used this exact same implementation of Background Worker before in a GUI app with no such peculiarities.

    So is there a background worker - like solution for console apps, or are you saying I need to work in a Synchronization Context?

    I have a very "typical" need in that I have some "calculator" code that can run for hours and days and yet I still need to monitor for user/io (ie in a main thread). 

    Michael

    The basic problem is that a Console application doesn't really have a "main thread" with the same requirements as a GUI application - you can use Console from any thread, so there isn't a requirement for a message pump, in which case you get no synchronization context.

    What is your scenario?  There are plenty of other options for threading with specific order requirements that work in a console application.  For example, many scenarios can be handled via BlockingCollection<T> - a thread can use BC.GetConsumingEnumerable() to "parse" items in order that they were queued, and items can get queued from any thread at any time.  This can allow you to use Tasks (Task.Factory.StartNew) to start work at any point, etc.


    Reed Copsey, Jr. - http://reedcopsey.com
    If a post answers your question, please click "Mark As Answer" on that post and "Mark as Helpful".

    Friday, May 18, 2012 7:44 PM
    Moderator
  • Thanks for your continued replies on this. I've been reading: http://msdn.microsoft.com/en-us/magazine/gg598924.aspx  and my eyes are crossed already, lol.

    My specific case is that I have a console app that is handed some parameters at startup, and then must perform a potentially very lengthy calculation.

    While the calculation is taking place,the "main thread" needs to do something useful with the progress event data produced by the BW,  (it currently writes it to a series of progress files on disk) and it watches for the presence of a "stop file" to show up on disk, and if it does, to cancel the calculation. If the calculation ends and no stop-file shows up. a results file is written from data that comes from the "done" event handler, and the app exits.

    So from one perspective, it has a lot of the same needs as if it were in a GUI environment - it needs to be doing something in the background while still being responsive to other non-related (user initiated) events.

    M


    Friday, May 18, 2012 7:57 PM
  • I don't see the purpose of the separate console application.  Why can't you start the BGW thread from the main thread?
    Friday, May 18, 2012 8:00 PM
  • A tiny bit of history: my app was originally a GUI app that performed a variety of calculations, each of which could take hours, or a few days, to complete.  I designed a base calculator class which wrapped up a BW, and then each "kind" of calculator inherited from this base class, overrode certain methods to provide unique calculations, unique progress data, and unique final data. And all was well.

    Until the client started supplying us with huge problems to be calculated, which could run for years and beyond.

    So my task was to somehow "distribute" the calculation so that we could have more than one "core" working on a calculation at a time and thus it would collectively  finish in a reasonable amount of time. In a "typical" case, we might need a couple hundred cores to finish some of these calcs in a weekend worth of time.

    The challenge was how to architect this in software. I already had a nice calculator class in the GUI app and I reasoned that the simplest way to architect a solution would be to create a console app that uses this same class. Each instance of this console app would be running the calculator, but on only "part of the problem" (no need to go into those details here).  So ultimately, if we determine we need to speed up a given calculation by a factor of 300, we will launch 300 instances (using Amazon or other cloud servers) of these calculator console applications.

    As I mentioned in the previous post, each of those instances will be doing a lot of crunching, but it also has some i/o needs it is responsible for; it has to look for a "stop-file" in case we want it to stop early, it has to emit progress files, and must ultimately produce a result file.

    So the challenge is how to get both the calculation logic and the user i/o handled in a console app.  It's the exact same need one would have in a GUI app, it just happens to be packaged in console app..

    Hope this helps

    Michael

    Friday, May 18, 2012 8:19 PM
  • " I designed a base calculator class which wrapped up a BW, and then each "kind" of calculator inherited from this base class, overrode certain methods to provide unique calculations, unique progress data, and unique final data."

    Each class is a BGW, so when a class runs, it runs on its own thread and if there are enough cores, it's own core.  Why can't this be scaled?  You can have many BGW's reporting to a single synchronization context.

    Friday, May 18, 2012 8:32 PM
  • I'm not sure what you are telling me here.. Yes, each instance of my "calculator class" launches a BGW, and so if I created 5 instances of my classes inside a given application, I'd end up with 5 BGW's.  But in my console app, I can't even get ONE of them to work correctly because, apparently, of the problems with lack of a synchronization context.  So to me, the first challenge seems to be solve that problem.

    Seems like some answers might be:

    1. Change the app to be a GUI app. I don't need a GUI app, but if that solves my synchronization problems, then I guess I can do that

    2. Rewrite my calculator logic to not use BGW  and to write the "threading logic" myself instead

    3. Use some of the newer parallel computing stuff from NET 4.0

    4. Use some 3rd party library that Reed suggested: http://nitoasync.codeplex.com/releases/view/33364

    5. Not use threads; each instance of my console app would run the calculation code itself, periodically handling the user/io needs (writing out the progress file, looking for the stop-file, etc)


    Michael

    Friday, May 18, 2012 8:47 PM
  • Your option 1 would be the simplest - if you had a "hidden" form (that didn't display), you could still have a Sync context installed, and use that as your "main" thread... Since you started as a GUI application, it would probably "just work".

    The Nito library would provide a simple way to install your own SynchronizationContext, which again, would make things just work.

    Otherwise, you're probably going to have to rework your code.  Frankly, if you're not using a GUI application, BackgroundWorker is (IMO) more trouble than its worth - there are nicer alternatives, especially with all of the new TPL constructs and cancellation options in place.  Most of what is done with a BW can easily be done via Tasks with continuations (see http://reedcopsey.com/2010/04/19/parallelism-in-net-part-17-think-continuations-not-callbacks/) in a way that really "flows" more logically, and ties into the .NET 4 cancellation model (http://msdn.microsoft.com/en-us/library/dd997364.aspx) well.


    Reed Copsey, Jr. - http://reedcopsey.com
    If a post answers your question, please click "Mark As Answer" on that post and "Mark as Helpful".

    • Marked as answer by Veloz Monday, May 21, 2012 3:59 PM
    Friday, May 18, 2012 9:20 PM
    Moderator