Questions about multiple threads and AppDomains in Windows Forms

There's a set of questions that come up on a regular basis from customers concerning mechanisms to partition Windows Forms applications in one way or another. We tend to get them in three basic flavors:

1) I want to improve performance by having each of my child forms/MDI child forms/UserControls running in a different thread

2) I want to improve robustness by having each of my forms in a different AppDomain

3) I want to be able to unload things so I want to load them into a separate AppDomain

The unfortunate reality most of these things do not result in the behavior the customer expects, and (at best) carry significant complexity and performance costs. The general issues around each one, in order:

1) There are two issues here. One is that you need to synchronize/marshal all the cross-thread communication between these pieces which is error prone (to marshal the calls that you are making), or impossible (to do the internal places where Windows Forms doesn't expect there to be threading issues). The second issue, is that all this syncronization, plus the synchronization that Windows will do for any message traffic between the pieces will erode more of the benefit you hope to get by separating the threads to begin with. You're add a bunch of complexity for questionable benefit. In these situations, you should be pushing the work to a background thread(s) and then marshalling over the data to update on a single UI thread.

2) Yes, AppDomains sure do sound cool, don't they? Upon discovering that these exist, many users are enticed by the wonderful possibilities that they seem to enable. Even we on the Windows Forms team had a brief fling with such idea - we wanted to put the designer into it's own AppDomain for a few reasons. But it is not so. AppDomains were primarily targeted at the ASP.NET scenario, where you want to have logical separation of tasks within the same process, but little or no communication between them. The analogy of a separate process really is the right one here. Everything is setup separately between them: types, data, security evidene, everything. And any calls between them are basically RPC; they need to be serialized across the boundry. All of this ends up incurring a significant working set and performance cost, and leads to a bunch of sticky issues that make it basically untenable. The fact that Control is a MarshalByRefObject doesn't help this scenario at all -- even if Control itself could be marshalled over, it's API members don't support this.

3) This has the same issues as (2) above but often tends to not work if you have any communication at all. The idea of unloading is that you can unload any Assemblies that you load through your AppDomain, which is fine but it's very easy to "leak" types from one AppDomain to another if there is any significant cross-communication going on. This was one of the main problems with separating the designer: things like the PropertyBrowser would need to be duplicated as well (or a bunch of proxy systems built) because as soon as it gets a hold of a type, you can't unload it anymore.

So are there any cases that this stuff works? Yes, there are. For threads, there are many cases where you can use threading so long as you're not doing any parenting between them, they each have their own message pump, and you are careful to use Control.Invoke to marshal/synchronize calls across threads. Pretty standard stuff.

The AppDomain situation is similar but there really can't be any communication from one to the other. If you wanted to write a Windows Forms application in the explorer.exe model where you have mulitple top-level windows that share a process, you can get that to work. Basically, follow the advice of Dr. Egon Spengler and "don't cross the streams!".

Print | posted @ Friday, April 08, 2005 1:01 PM

Comments have been closed on this topic.