Wednesday, August 18, 2004

Nasty Windows Forms Bug - Hidden Windows, Worker Threads and Delayed Handle Creation

An issue came up on the project I am working on at the moment where one of the applications was freezing up during a population of the UI from data that was being sent over a web service. All the code to correctly manage windows calls being made on the correct thread was being automatically generated, so it was a pretty big surprise that the problem cropped up. The freeze was pretty easy to replicate, and after setting up full debug symbols for Windows and the .NET framework, it was apparent that the call that was hanging was the Win32 function SetWindowsPos.

After a bit of frigging around, we noticed that although the callback that occurred when the web service ended (which was obviously occurring on a thread pool worker thread and not the UI thread) was actually making direct calls against Control-derived objects. The code is this method was correct - we were checking InvokeRequired, and had the logic to Invoke back onto the UI thread if created, but InvokeRequired was returning false in our case. Looking at the logic of InvokeRequired, the handle of the current thread was being compared to the handle of the thread that the Windows handle was created on, which was the same in this case. This occurred despite the fact that the Control-derived object that we were accessing was created back on the UI thread. What the hell was happening?

A bit more investigating confirmed that Windows handles are not created until they are actually required. The Handle property only creates the real Windows handle on the first get_ call, which doesn't occur when a Control-derived object is created. The problem in this case was that the window being populated was not actually being shown until the data had come back from the web service, so no one had asked for the handle until it was accessed as part of the InvokeRequired check. This in turn result in the handle being created on the worker thread, which we did not own, and which didn't have a message pump set up to handle windows calls. The result - the app locked up when other calls where made to the previously hidden window, as these calls where made on the main UI thread, which reasonably assumed that all other Control-derived object had also been created on this thread.

The work-around is simple - access the handle property somewhere during the Control-derived objects creation, which forces the real underlying Windows handle to be created. After that, all works well. The problem was found in the .NET Framework V1.1, and in still there in the current Beta 1 release of the 2.0 Framework. We've submitted the problem to Microsoft, and I'll update you when we get word back. The fix is reasonably simple - they need to track the handle of the thread that the object was created on, and if this is different when the Handle property is accessed for the first time, the call should go back to the object-creating thread.

A simple re-pro that shows the handle being created on the wrong thread is shown here


Post a Comment

<< Home