Thursday, June 25, 2009

Threading Needles and the BackgroundWorker

I'm not going to lie, sometimes, I'm just so irked at myself for doing something just... stupid, or worse, implementing something that I've not gotten a complete and thorough understanding of.

And yes, I've implemented code structures which I've not understood in their entirety many, many times. I'm a consultant, it's par for the course. Half of my job is just getting a firm enough grasp of various coding scenarios that I can interpret and fix whatever error is cropping up.

Regardless, I've been so engrossed in WPF and just ensuring that things work as I expect (and hope) that I've not paid too much attention to the exact specifics of what was happening. A fact which slapped me in the face, quite hard, when I realized my current application was taking upwards of 8 seconds to save a page and advance to the next one.

8 seconds for a windows-based application, that runs entirely on the client-side of things, is a heck of a long time. It's on the far side of truly usable, IMO.

So of course, I dive into the code and restructure and reorganize and refactor and all these other nifty terms. Whole structures are modified and I shave the time which the page takes to process from 8 seconds down to 2. At least that's the time that is being reported to me. The application was still noticeably showing lag bewteen shifting views. So I watched the second hand on my watch, and despite the 2 seconds being reported, it was closer to 8 on switching.

So, I closed my eyes, exhaled, and then worked until 1 in the morning hunting for the reason.

And I found it.

The page upon advancing, reported that fact to the parent form, which then passed a command to a secondary form to refresh the overview of the structure being operated upon. It was this refresh which was taking nearly 8 seconds.

But that confused me, as I thought that I had made that multi-threaded.

And while doing various readings is when I realized the cold hard truth about a bit of technology that I had utilized.

Basically, I had mis-read the role that Dispatcher has in multi-threaded systems. My thought, and I'm still not sure how I came to this one, was that by Invoking a function against the Dispatcher, it performed its tasks in a separate thread.

Boy, was I wrong.

And in case anyone is wondering, the 60,000 foot view is that the Dispatcher.Invoke, attaches the given function to the UI thread to allow safe access of the form's controls.

I felt the need to bang my head against the closest hard surface. Luckily for me, my desktop is somewhat covered in papers and books which aren't quite as hard as the wood surface itself.

So, with the cold, hard realization that Dispatcher.Invoke wasn't doing what I thought (which was spawn off a method to a new thread), I went forth to find something that would do it effectively.

And thus, I found the BackgroundWorker class.

This is my new best friend.

Here's how the BackgroundWorker works. You instantiate it as a new object, and then you set up a couple of event handlers for it. There's three possibilities:

  1. DoWork
  2. ProgressChanged
  3. RunWorkerCompleted
The way those work are simple, in the DoWork handler, you perform the actual functionality that you're trying to do. If you wish to report progress back to the user, then you tell the object that you want to report some progress (which takes an integer representing the percentage down, and an object that can be basically anyother data you desire).

The BackgroundWorker reports being done whenever it reaches the end of the DoWork handler. And again, there's an object that gets passed around from one portion to the other for you to play with.

Then of course, I ran up against the need to be able to spawn up multiple instances of these things. Well, an array of BackgroundWorkers sounds like a horrible, horrible idea, so I once again turned to my research skills.

And thus found the Thread Pool.

Again, it's an object which you can create, and then utilize to spawn off a function (in this case passed as a delegate) in a separate thread.

The thing about it is, that it's a smart object and watches how much the CPU is being utilized and determines whether to create a new thread, or wait for one of its current ones to finish as you add things to it for processing.

The bad news is that there's nothing that tells you when an item is finished. Therefore, you must use Dispatcher.Invoke in order to pass information back to the user interface.

Now, watch that I've learned all that hassle, that Microsoft will go and change everything in .NET 5 or something silly like that.

Blog Widget by LinkWithin