views:

305

answers:

2

I have a sort of plug-in model in which various complex user controls are stored in DLLs and loaded and instantiated at run time using

Activator.CreateInstanceFrom(dllpath, classname).

Since I'm loading quite a few of these I wanted to do it in the background, keeping my UI responsive, by creating a new thread to do the loading. The controls are then parented to the main form and displayed when needed.

This seems to work fine - until I try to set any property on any nested control on one of these user controls, e.g. in the event handler of a button, which throws a cross threading exception. I do realize I could avoid this by checking InvokeRequired every time I access a property, but I'd rather not have to worry about that when writing the code for the user controls (especially since there are others writing these bits of code too who might not always remember).

So my question is, is there any safe way to do what I'm attempting, or how should I best go about loading these controls in the background? Or is it basically impossible and do I have to stick to the main thread for creating controls?

I hope that the information I've provided is enough to make my situation clear; if not I'd be glad to elaborate and provide code samples.

+1  A: 

The code sample in this answer provides an elegant way around this problem.

Code quoted from that answer:

public void UpdateTestBox(string newText)
{
    BeginInvoke((MethodInvoker) delegate {
        tb_output.Text = newText;
    });        
}

...although in your case you would want to call BeginInvoke on the controls themselves:

public void UpdateTestBox(string newText)
{
    tb_output.BeginInvoke((MethodInvoker) delegate {
        tb_output.Text = newText;
    });        
}
Fredrik Mörk
I don't quite see yet how this will help. I mean, yes, you're right, of course, using Invoke/BeginInvoke will help, but that's exactly what I'm trying to avoid. Or perhaps I just haven't understood what you're getting at.
Tom Juergens
If you create the controls on a different thread than the UI thread I cannot see how you will get away from Invoke/BeginInvoke. However, the suggested code is a lot simpler than the if(ctl.InvokeRequired) [...] variant. The immediate punishment of going multithreaded is that you need to do your synchronization ;)
Fredrik Mörk
+2  A: 

Is fine to load the DLLs and create the control objects in the background, but the control has to be added to the form in the main thread and all user interaction as well as any programmatic change of control properties (after it was created) has to occur in the main thread. There is simply no way around this, as you probably already know. Now unless the load of those DLLs and control creation takes ages, I see no reason to do it in separate background threads.

If certain actions performed by the controls are blocking the UI and you want them to occur in the background, this is a different story, and you better consider each action on an individual basis and create explicit methods to communicate between UI thread and background threads.

Attempting to do a generic one-fits-all framework that works the same in UI thread mode and in background mode and simply checks the InvokeRequired results sometimes in worse performance (as all threads are blocked in the Invoke) or even in (undetected) deadlocks as soon as the application reaches a reasonable complexity. Also doing all updates async in BeginInvoke, w/o considering each method individually, can result in data consistency problems (ie. controls may update themselves to states back in time due to reversing of the invocation order between threads).

Remus Rusanu
Yes, I have the feeling that I was trying to be just a bit too clever. I've more or less abandoned my first attempt and am moving along lines similar to your second paragraph. Thanks for the tips in par. 3; I hadn't realized that.
Tom Juergens
Personally I prefer to use a queue (http://msdn.microsoft.com/en-us/library/7977ey2c.aspx) to communicate between the threads: the background thread adds items ('results') to the queue, the UI thread check the queue on Idle and updates the controls (this is typical producer/consumer). But you must consider carefully if the background cannot outpace the ability of the UI to keep up, which can easily happen, specially with grids, causing the queue to grow out of control unless you take special measures.
Remus Rusanu