We have been creating a major .NET WinForm application that will be deployed in Citrix for a few months now. Basically, think of it as a custom rewrite of MS Access (note that this wasn't a techie's idea) that works against MS SQL Server. Of course, business as usual doesn't like any limitations, so we have way too much data and far too many features to work in a shared environment like Citrix. Or do we? That has been the bane of my existence for the last couple of months, and unfortunately there doesn't appear to be many others that have such experience to share. My main concern has been whether or not the .NET garbage collector would see the 4GB of RAM and think it could have it all, unaware of the other users on the box. I have posted that concern to various forums several times, and I've seen a few others with similar concerns, but no one ever answered, other than people telling about .NET works in general without any Citrix experience.
Well I can finally report that things look pretty good afterall -- after much painful performance tuning and tracking down a memory leak in a 3rd party grid control. There were many small performance gains, but the biggest one is something I still don't understand, but the numbers are proof. Basically, we have a business class that has a method that gets a DataSet and stores in one of its members, and then the calling class separately sets the grid's datasource to the class property that exposes that member. It seems like that would be fine, since a DataSet is a reference type, but it turns out that setting both the class member and the grid datasource in separate steps takes twice as much time as having the class method set the member and then return it to the caller! I would love it if someone can explain that one to me -- my team told me it didn't work this way, but the numbers are proof, and none of us understand it.
So we were hoping for good results with our test this week -- sure enough, the first 45 minutes testing the application in Citrix with 15 users looked very good from a user's point of view. But then things started grinding to a halt, and didn't pick up for 30 minutes when most of us started closing and restarting the application. The naysayers in our company quickly released their observations that our application could not even support 15 users, which isn't very good in an expensive Citrix environment. The infrastructure guys pointed to some poor .NET performance counters -- but wait, those numbers didn't get bad until 15 minutes after everything went to crap, and all the .NET numbers before then looked sweet. The problem instead is obvious when you look at the overall memory numbers -- there was a gradual memory leak, unrelated to .NET, that eventually swamped the system, and eventually that also affected the .NET numbers. I finally found the control, the 3rd party grid, that had the memory leak, and it turns out they already knew about this and had a hotfix, not to mention a newer version that we were still investigating.
We will now start updating that grid control, and I hope to soon be able to report a better test result in Citrix. There may still be problems, but the numbers once again speak for themselves, and I think its pretty clear that our .NET app ran better than Access and our older Access-like applications that we are trying to replace. The naysayers see .NET start with 20MB and freak out saying that its not going to scale, but that initial grab doesn't mean it takes 20MB to run the app, it just means .NET is getting memory so it doesn't have to later. Its actually quite an incredible feeling to look at the graphs of .NET's memory and garbage collection when you have 15 people on a Citrix box, each looking at about 3 separate instances of our main view, each having an average of 25000 rows of data (some much more), averaging 25 columns (but some with more again). .NET works great, and so does MS SQL Server, which we use exclusively with some of the world's largest databases -- that's right, we don't have any instances of Oracle at all. Life is good.
Update: Life is NOT good since none of the global .NET memory performance counters that I relied on actually work!