Friday 24 July 2009

To make the life in the multithreaded world easier 1

I haven't done any serious windows forms development till this recent 6 months. Prior to that, I may have done some small desktop tools here and there, but they are in no means serious enough. Most of my work in the past were Asp.Net, MOSS, SQL Server... but no Windows forms applications.

In my recent windows application development work, I came across a lot of code like this:

private void DoSomethingFunction()
{
if(this.InvokeRequired)
{
this.BeginInvoke((System.Threading.ThreadStart)delegate()
{
DoSomethingFunction();
});
return;
}

// Do something here
}
"this" in the code snippet is normally some windows forms control or user control. In .Net Framework 2, cross-thread accessing to a windows forms control is not allowed. So if you have some multi-threading code, you'll need to check the "InvokeRequired" property to determine if this method is invoked by a worker thread or the main thread, and do things appropriately for the two scenarios.

But there is this "hidden" issue with "InvokeRequired", which is not documented. After we have this application deployed to UAT, the testers reported that sometimes (most of the time it's when they first launched the application), if they didn't wait for a few seconds, but click straight to the left hand side navigation node to open a tab window, the application simply went hanged.

This was an issue of intermittent nature and hard to re-produce this in the developer's machine. I studied the code again and again and couldn't see anything wrong.

Luckily I found this very good article "Mysterious Hang or The Great Deception of InvokeRequired" at http://ikriv.com:8765/en/prog/info/dotnet/MysteriousHang.html. This nice guy presented a thorough case study on why your .Net 2 multithreaded UI may hang from time to time.

This issue is related to the fact that .Net defers the creation of real win32 window as much as possible due to performance consideration. The window handle will be created when the control needs to be actually shown on screen or when the control.handle is requested programatically. Before a real window handle gets created (rarely but due to some race condition), the control.InvokeRequired may return a deceptive value (it always returns false in that case), and the calls from a non-GUI worker thread to the GUI might cause some unexpected behaviour.

Back to our application, when the tab form is loading, we have worker threads to do things asynchronously to make the UI more responsive to the end users. When the async work is done, the worker thread will then try to update the UI and that's when the "InvokeRequired" property is checked. However, as mentioned above, at that time if the win32 handle for our tab form hasn't been created at all, the mysterious hang will kick in. Mystery solved!

The quick fix is when registering the view (the windows control) with the controller (MVC pattern), call the control.Handle programatically before firing up any async calls.

No comments: