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.
![Smile :) :)](/styles/default/custom/smilies/smile.gif)
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