Re: Load secondary form in backgroundworker thread



On Mon, 25 Jun 2007 17:15:43 -0700, RvGrah <rvgrahamsevatenein@xxxxxxxxxxxxx> wrote:

I have an app whose primary form will almost always lead to the user
opening a certain child window that's fairly resource intensive to
load. Is it possible to load this form in a backgroundworker and then
use the Show method and hide method as necessary?

IMHO, the correct design is to use a BackgroundWorker instance to run the code that initializes the data for the form, and then when that initialization is done, create the form on the main thread. Note that this means you won't be doing the "fairly resource intensive" work from within the form class. Instead, you need to do it somewhere else in a way that can then be passed to the form when it's shown.

You could do other designs. For example, have the form itself do the manipulation of the BackgroundWorker to get the initialization to happen when the form is shown. But this would require overriding the Show() method so that it starts the initialization if necessary rather than actually showing the form, and makes for a somewhat confusing design IMHO. Putting the initialization code outside of the form makes clear how to deal with issues like "what happens if I try to close the form before its initialization has finished?" for example.

Anyone know of
simple sample code showing this? I understand that the windows class
is single threaded, so there are probably issues I'm not understanding
yet.

Important things to know:

* A window is owned by the thread on which it was created. A message queue exists for that thread, and messages for that window are put in that queue.

* Even a Form class instance isn't "single-threaded" per se. In spite of the fact that you need to make sure that window message-based interactions with the window happen on the owning thread, other interactions with the class instance can happen on any thread as long as you make sure that access to data within the class is synchronized.

Once you use a backgroundworker to do something with a class or object
in general, are you forever bound to dealing with this object through
the backgroundworker, or can control be handed to the bw to do
something and then when the bw says it's done, you can use the direct
reference again?

Aside from occasional issues like the message queue I mentioned above, data instances are completely independent of threads. Any thread can operate on any data. The tricky part is that any thread can operate on any data. :) This means that if you have data that is visible by more than one thread, you need to include code to synchronize access to the data, to ensure that only one thread is manipulating a specific piece of data at a time.

A very simple synchronization method would be to use the Control.Invoke() method (since Form inherits Control, every form has this Invoke() method). The Control.Invoke() method takes a delegate and executes it on the same thread that owns the Control instance. This means that if you use Invoke() from any other thread to manipulate data used by the rest of your program, you can be assured that the access to that data is done only on the main thread and will be synchronized with any other access to that data that is also done on the main thread.

Other synchronization mechanisms include using the "lock" statement, the Monitor class, a WaitHandle, a Mutex, etc. It happens that there are a lot of different synchronization methods, each suited to particular kinds of inter-thread communications.

In your case, since you are already using BackgroundWorker, and because the RunWorkerCompleted event gets executed on the main thread, it is essentially doing an implicit Invoke() for you. I think this is the simplest solution in your case.

Looking at your example pseudo-code:

Pseudo code in form1:

form2 f = new form2();

Instead of this, start the BackgroundWorker doing the initialization.

use bw to load f

(bw says f it's done loading f)

See above. You don't "load" a Form instance, so much as you simply create it. That instance may in turn load things, but the instance itself is created as soon as you're done executing the constructor.

So, don't use the BackgroundWorker "to load f". Use it to initialize whatever data will be needed when you show your form.

in other form1 procedures:

if (f.loaded) f.show.....

Add a handler to the BackgroundWorker.RunWorkerCompleted event, in which you actually create and show the form, using the data initialized by your BackgroundWorker.

As a rough example:

void buttonChildForm_Click(object sender, EventArgs e)
{
BackgroundWorker bw = new BackgroundWorker();

bw.DoWork += InitChildFormData;
bw.RunWorkerCompleted += DoneChildFormInit;

bw.RunWorkerAsync();
}

void InitChildFormData(object sender, DoWorkEventArgs e)
{
// do whatever you need to initialize the data. Store the result in
// e.Result
}

void DoneChildFormInit(object sender, RunWorkerCompletedEventArgs e)
{
Form2 form2 = new Form2(e.Result);

form2.Show();
}

Of course, you will need to cast e.Result back to whatever is the appropriate type to construct Form2. I didn't bother writing the cast because I'd have to pick some bogus type name and I figured that would complicate the example more than just having no casting at all. :)

Pete
.


Loading