Debugging Windows Forms Application Hangs During SystemEvents.UserPreferenceChanged

Hi Everyone,

One of the most common issues we debug related to Windows Forms applications is when an application becomes unresponsive due to the SystemEvents.UserPreferenceChanged event. This blog to explain what is actually occurring, and how to find and fix the culprit. The reason this occurs and the solution is explained in KnowledgeBase article 943139.

Windows Forms application freezes when system settings are changed or the workstation is locked
support.microsoft.com/kb/943139/EN-US

For the purposes of this discussion, I’m going to talk mainly about System.Windows.Forms.Control or just “Control” (remember that a Windows Form itself is a derivative of the Control class).

Why Windows Forms Applications Become Unresponsive During SystemEvents.UserPreferenceChanged

As KB article 943139 mentions, the application may become unresponsive when a Windows Forms Control has been created on a thread which doesn’t pump window messages; and when the application’s UI thread receives and processes a WM_SETTINGCHANGE message. Note the window message could be for other “settings” related messages such as WM_SYSCOLORCHANGE, WM_DISPLAYCHANGED, WM_THEMECHANGED, among others.

The SystemEvents class uses a hidden window internally to handle processing the window messages related to the SystemEvents class. If you were to look at this under Spy++, the window caption for this hidden window starts with “.NET-BroadcastEventWindow”.  So when a WM_SETTINGCHANGE (or related) message comes in, the Winforms message pump retrieves it from the thread’s message queue and dispatches it to the window procedure associated with the “.NET-BroadcastEventWindow”. This uses the thread’s SynchronizationContext.Send method to invoke the delegate which eventually raises the SystemEvents.UserPreferenceChanged event for the target window.

Windows Forms installs a WindowsFormsSynchronizationContext on each thread which has created a Windows Forms Control. This derived SynchronizationContext is used by Windows Forms to handle communications between various threads with Windows Forms controls. So the delegate which SystemEvents wants to invoke eventually raises UserPreferenceChanged is invoked, it is done so on the WindowsFormsSynchronizationContext.  The internal implementation of WindowsFormsSynchronizationContext uses a Windows Forms control known as the “Marshaling Control” to handle communication between various threads, and subsequently the overridden SynchronizationContext.Send method uses Control.Invoke to marshal the call from the current thread to the target thread. In order for Control.Invoke to return, the target thread must be pumping window messages.When the target thread does not pump window messages, the UI thread is going to block indefinitely, and now you’re hung.

Debugging a Windows Forms Application in the Unresponsive State

So what does this look like under the debugger? Alright, let’s debug this after we are in the hung state to see what’s happening. The rest of this blog assumes you are familiar with the use of Windbg, user mode and .NET debugging. If you attach Windbg to a Windows Forms application in this state, your main UI thread should look something like this.

 0:000>knL
 # ChildEBP RetAddr
00 0018e2e0 75340bdd ntdll!NtWaitForMultipleObjects+0x15
01 0018e37c 758a1a2c KERNELBASE!WaitForMultipleObjectsEx+0x100
03 0018e418 76da2bf1 USER32!RealMsgWaitForMultipleObjectsEx+0x14d
04 0018e444 76d9202d ole32!CCliModalLoop::BlockFn+0xa1
05 0018e4c4 79e0d245 ole32!CoWaitForMultipleHandles+0xcd
06 0018e4e4 79e0d1a6 mscorwks!NT5WaitRoutine+0x51
07 0018e550 79e0d10a mscorwks!MsgWaitHelper+0xa5
08 0018e570 79ec42d8 mscorwks!Thread::DoAppropriateAptStateWait+0x28
09 0018e5f4 79ec436d mscorwks!Thread::DoAppropriateWaitWorker+0x13c
0a 0018e644 79e64af8 mscorwks!Thread::DoAppropriateWait+0x40
0b 0018e748 791169df mscorwks!WaitHandleNative::CorWaitOneNative+0x156
0c 0018e764 79116995 mscorlib_ni!System.Threading.WaitHandle.WaitOne(Int64, Boolean)+0x2f
0d 0018e77c 7b3f2231 mscorlib_ni!System.Threading.WaitHandle.WaitOne(Int32, Boolean)+0x25
0e 0018e798 7aed4c20 System_Windows_Forms_ni!System.Windows.Forms.Control.WaitForWaitHandle(System.Threading.WaitHandle)+0x89
0f 0018e830 7b3f3bb0 System_Windows_Forms_ni!System.Windows.Forms.Control.MarshaledInvoke(System.Windows.Forms.Control, System.Delegate, System.Object[], Boolean)+0x31c
10 0018e86c 7b621873 System_Windows_Forms_ni!System.Windows.Forms.Control.Invoke(System.Delegate, System.Object[])+0x50
11 0018e884 04f42496 System_Windows_Forms_ni!System.Windows.Forms.WindowsFormsSynchronizationContext.Send(System.Threading.SendOrPostCallback, System.Object)+0x5f
12 0018e8b8 04f4064f System_ni!Microsoft.Win32.SystemEvents+SystemEventInvokeInfo.Invoke(Boolean, System.Object[])+0x62
13 0018e904 04f4195b System_ni!Microsoft.Win32.SystemEvents.RaiseEvent(Boolean, System.Object, System.Object[])+0x10f
14 0018e920 04b85989 System_ni!Microsoft.Win32.SystemEvents.OnUserPreferenceChanged(Int32, IntPtr, IntPtr)+0x77
15 0018e98c 009f09e4 System_ni!Microsoft.Win32.SystemEvents.WindowProc(IntPtr, Int32, IntPtr, IntPtr)+0x189
WARNING:Frame IP not in any known module. Following frames may be wrong.
16 0018e9b0 770662fa 0x9f09e4
17 0018e9dc 77066d3a USER32!InternalCallWinProc+0x23
18 0018ea54 770677c4 USER32!UserCallWinProcCheckWow+0x109
19 0018eab4 7706788a USER32!DispatchMessageWorker+0x3bc
1a 0018eac4 00491a32 USER32!DispatchMessageW+0xf
1b 0018eae0 7aed8cee CLRStub[StubLinkStub]@491a32
1c 0018eb94 7aed8957 System_Windows_Forms_ni!System.Windows.Forms.Application+ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(Int32, Int32, Int32 +0x24e
1d 0018ebec 7aed87a1 System_Windows_Forms_ni!System.Windows.Forms.Application+ThreadContext.RunMessageLoopInner(Int32, System.Windows.Forms.ApplicationContext)+0x177
1e 0018ec1c 7ae95911 System_Windows_Forms_ni!System.Windows.Forms.Application+ThreadContext.RunMessageLoop(Int32, System.Windows.Forms.ApplicationContext)+0x61
1f 0018ec34 004600ae System_Windows_Forms_ni!System.Windows.Forms.Application.Run(System.Windows.Forms.Form)+0x31
20 0018ec40 79d61b4c UserPreferencesHang!UserPreferencesHang.Program.Main()+0x3e

You can see in the above callstack the same sequence of events I described earlier. In this particular case, a WM_SYSCOLORCHANGE message was dispatched to the window procedure.

The window procedure handles this by eventually calling Microsoft.Win32.SystemEvents+SystemEventInvokeInfo.Invoke. This obtains the thread’s SynchronizationContext (in this case, the WindowsFormsSynchronizationContext) and calling its Send method. This calls into Control.Invoke, which then calls into System.Threading.WaitHandle.WaitOne, etc. which we eventually see stuck in kernel32!WaitForMultipleObjects. Since the target thread isn’t pumping window messages, we are blocked indefinitely. To determine the target thread, we need to do some managed debugging using the managed debugger extension (sos.dll).

First, load sos.dll into the debugger using the .loadby command. NOTE: If using .NET 4.0 / 4.5, use "clr" instead of "mscorwks" below.

 0:000> .loadby sos.dll mscorwks

Let’ s examine the thread’s managed stack objects, looking for the lowest most instance of a WindowsFormsSynchonizationContext object.

0:000>!dumpstackobjects
OS Thread Id: 0x19ac (0)
ESP/REG Object Name
0018e5e0 0259dd78 System.Windows.Forms.WindowsFormsSynchronizationContext
0018e66c 025ebbc4 System.Threading.ManualResetEvent
0018e680 025ebbc4 System.Threading.ManualResetEvent
0018e6e8 025ebbc4 System.Threading.ManualResetEvent
0018e6ec 025de4ac System.Windows.Forms.Application+MarshalingControl
0018e6f8 025ebbc4 System.Threading.ManualResetEvent
0018e700 025de4ac System.Windows.Forms.Application+MarshalingControl
0018e730 025ebbdc Microsoft.Win32.SafeHandles.SafeWaitHandle
0018e760 025ebbc4 System.Threading.ManualResetEvent
0018e778 025de4ac System.Windows.Forms.Application+MarshalingControl
0018e78c 025eba7c System.Windows.Forms.Control+MultithreadSafeCallScope
0018e7cc 025de514 System.Windows.Forms.PropertyStore
0018e7d4 025de4ac System.Windows.Forms.Application+MarshalingControl
0018e7d8 025ebb10 System.Collections.Queue
0018e7dc 025de4ac System.Windows.Forms.Application+MarshalingControl
0018e7e0 025ebad0 System.Windows.Forms.Control+ThreadMethodEntry
0018e7e4 025eba88 System.Threading.ExecutionContext
0018e7e8 025de4ac System.Windows.Forms.Application+MarshalingControl
0018e7ec 025de4ac System.Windows.Forms.Application+MarshalingControl
0018e7f4 025de4ac System.Windows.Forms.Application+MarshalingControl
0018e7fc 025de4ac System.Windows.Forms.Application+MarshalingControl
0018e824 025eba7c System.Windows.Forms.Control+MultithreadSafeCallScope
0018e828 025eba48 System.Threading.SendOrPostCallback
0018e82c 025de4ac System.Windows.Forms.Application+MarshalingControl
0018e83c 025eba68 System.Object[] (System.Object[])
0018e840 025eba48 System.Threading.SendOrPostCallback
0018e844 025eba7c System.Windows.Forms.Control+MultithreadSafeCallScope
0018e85c 025de014 System.Threading.Thread
0018e860 025eb558 System.Object[] (System.Object[])
0018e864 025de4ac System.Windows.Forms.Application+MarshalingControl
0018e868 025eba48 System.Threading.SendOrPostCallback
0018e874 025eba68 System.Object[] (System.Object[])
0018e878 025eba68 System.Object[] (System.Object[])
0018e87c 025eba48 System.Threading.SendOrPostCallback
0018e880 025de3f8 System.Windows.Forms.WindowsFormsSynchronizationContext
<snip>

I’ve highlighted the relevant WindowsFormsSynchronizationContext object above. Now we can dump it out to examine it.

0:000>!dumpobj 025de3f8
Name: System.Windows.Forms.WindowsFormsSynchronizationContext
MethodTable: 7af215b0
EEClass: 7ad06a94
Size: 20(0x14) bytes

(C:\Windows\assembly\GAC_MSIL\System.Windows.Forms\2.0.0.0__b77a5c561934e089\System.Windows.Forms.dll)
Fields:
      MT   Field Offset Type VT Attr   Value Name
79780f9c 40005c8       4 System.Int32 1 instance 0 _props
7af1f750 4003b24       8 ...ows.Forms.Control 0 instance 025de4ac controlToSendTo
7918a4d8 4003b25       c System.WeakReference 0 instance 025de40c destinationThreadRef
791647f4 4003b26       0 System.Boolean 1 TLstatic dontAutoInstall
    >> Thread:Value 19ac:0 18f0:0<<
791647f4 4003b27       4 System.Boolean 1 TLstatic inSyncContextInstallation
    >> Thread:Value 19ac:0 18f0:0<<
7918a00c 4003b28       8 ...ronizationContext 0 TLstatic previousSyncContext
    >> Thread:Value 19ac:0259dcd4 18f0:025de354 <<

We are interested in the destinationThreadRef member, which will be a WeakReference that points to our Thread of interest. Let’s dump the address of the destinationThreadRef member.

0:000> !dumpobj 025de40c
Name: System.WeakReference
MethodTable: 7918a4d8
EEClass: 78f4a22c
Size: 16(0x10) bytes 
(C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)

Fields:
      MT Field Offset Type VT Attr Value Name
791935f0 40005b4 4 System.IntPtr 1 instance 1c1778 m_handle
791647f4 40005b5 8 System.Boolean 1 instance 0 m_IsLongReference
 

The m_handle member is the address of a pointer to our Thread. So we must take this address, and look to see what address it points to.

0:000> dd 1c1778 l1
001c1778 025de014

Now we can dump the object located at that address to get our System.Threading.Thread object.

0:000>!dumpobj 025de014
Name: System.Threading.Thread
MethodTable: 791912fc
EEClass: 78f4d9a4
Size: 56(0x38) bytes
(C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)

Fields:
      MT Field Offset Type VT Attr Value Name
7918210c 400063f 4 ....Contexts.Context 0 instance 00000000 m_Context
7918da1c 4000640 8 ....ExecutionContext 0 instance 025de360 m_ExecutionContext
79190d28 4000641 c System.String 0 instance 00000000 m_Name
7919118c 4000642 10 System.Delegate 0 instance 00000000 m_Delegate
7918a424 4000643 14 System.Object[][] 0 instance 025de2a0 m_ThreadStaticsBuckets
79192ec4 4000644 18 System.Int32[] 0 instance 025de344 m_ThreadStaticsBits
79193924 4000645 1c ...ation.CultureInfo 0 instance 00000000 m_CurrentCulture
79193924 4000646 20 ...ation.CultureInfo 0 instance 00000000 m_CurrentUICulture
79190944 4000647 24 System.Object 0 instance 00000000 m_ThreadStartArg
791935f0 4000648 28 System.IntPtr 1 instance 6dc270 DONT_USE_InternalThread
79192f74 4000649 2c System.Int32 1 instance 2 m_Priority
79192f74 400064a 30 System.Int32 1 instance 3 m_ManagedThreadId
79169944 400064b 16c ...LocalDataStoreMgr 0 shared static s_LocalDataStoreMgr
    >> Domain:Value 00681bd8:025e2f54 <<
79190944 400064c 170 System.Object 0 shared static s_SyncObject
    >> Domain:Value 00681bd8:02532260 << 

The m_ManagedThreadId member above represents the managed thread ID of the thread of interest. So now we can dump all managed threads, looking for the thread with that ID. Note that we must look at the “ID” column in the following output for the managed thread ID.

IMPORTANT:  The ID column from the !threads output below represents the value in hexadecimal. Therefore, it is extremely important to convert the m_ManagedThreadId field from the output above to hex in order to look for the right thread. To do that, simply enter the following command into your debugger, using the integer value of the thread Id above. In this case, we use 0n3 to specify we want to see the hex representation of the integer value 3.

0:000> ?0n3
Evaluate expression: 3 = 00000003

In this case, 3 is the same representation in both decimal and hex, so that is what we look for in the output below.

0:000>!threads
ThreadCount: 3
UnstartedThread: 0
BackgroundThread: 2
PendingThread: 0
DeadThread: 0
Hosted Runtime: no
                                     PreEmptive GC Alloc Lock
       ID OSID ThreadOBJ State GC Context Domain Count APT Exception
   0 1 19ac 00685e30 2006020 Enabled 025f0b9c:025f1fe8 00681bd8 0 STA
   2 2 2124 006a1f80 b220 Enabled 00000000:00000000 00681bd8 0 MTA (Finalizer)
   4 3 18f0 006dc270 180b220 Enabled 025e3d90:025e3fe8 00681bd8 0 MTA (Threadpool Worker) 

Notice that the line with ID 3 (again, that’s our managed thread ID), corresponds to debugger thread 4 in the output above. That is the thread of interest that owns the window. Before switching to that thread in the debugger, note that the thread has is an MTA thread created in the .NET Threadpool. From this alone, we know there are potential problems. First, Windows Forms are only supported on threads using an STA model, and there are places in Winforms that may crash if used on a non-STA thread. The other thing we should be able to assume right away, is that since this is a .NET threadpool thread, it almost certainly will not be pumping messages. So let’s switch to that thread and find out.

 0:000> ~4s
eax=00000000 ebx=00000000 ecx=00000000 edx=00000000 esi=00000248 edi=081cf5dc
eip=77b9f8b1 esp=081cf594 ebp=081cf600 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
ntdll!ZwWaitForSingleObject+0x15:
77b9f8b1 83c404 add esp,4

0:004> knL
 # ChildEBP RetAddr
00 081cf594 75340a91 ntdll!ZwWaitForSingleObject+0x15
01 081cf600 758a1194 KERNELBASE!WaitForSingleObjectEx+0x98
02 081cf618 79d65497 KERNEL32!WaitForSingleObjectExImplementation+0x75
03 081cf65c 79d653e3 mscorwks!CLREventWaitHelper+0x2f
04 081cf6ac 79d65402 mscorwks!CLREvent::WaitEx+0x117
05 081cf6c0 79dcb75f mscorwks!CLREvent::Wait+0x17
06 081cf740 79dcbbed mscorwks!ThreadpoolMgr::SafeWait+0x73
07 081cf7a4 79ec305a mscorwks!ThreadpoolMgr::WorkerThreadStart+0x11c
08 081cf8c8 758a339a mscorwks!Thread::intermediateThreadProc+0x49
09 081cf8d4 77bb9ef2 KERNEL32!BaseThreadInitThunk+0xe
0a 081cf914 77bb9ec5 ntdll!__RtlUserThreadStart+0x70
0b 081cf92c 00000000 ntdll!_RtlUserThreadStart+0x1b

As you can see from the output above, this thread is pretty much what we thought it would be. It is a .NET threadpool thread sitting in WaitForSingleObject, waiting for the CLR to hand it some work. It’s definitely not pumping window messages, so the UI thread stays blocked indefinitely. So the resolution to this issue is to do what KB article 943139 mentions: create your Windows Forms controls on an STA thread which routinely pumps messages for the duration of the thread. 

We know the resolution to the issue, but what if you don’t know what code was responsible for creating the Control(s) on the worker thread? This is really the crux of the problem. The key is being able to find what code is creating these Controls, and the rest of this blog will focus on how to determine that.

Using Windbg to Determine When Windows Forms Controls Are Created

It is certainly easy enough to set a breakpoint on the constructor for System.Windows.Forms.Form or System.Windows.Forms.Control, but the problem with that approach is that it will be hit every time your main UI thread creates an instance of one of these objects. We want to limit our output to determining when code is creating Controls on threads other than your primary UI thread. The easiest way to do that is to set a breakpoint on the constructor for the System.Windows.Forms.Application+MarshalingControl type. Whenever any Control is created, Windows Forms first looks to see if it needs to create an instance of the MarshalingControl discussed earlier. The MarshalingControl is created once per thread, the first time anything creates an instance of a Control on that thread. This will track the first time any Control is created on your main UI thread, but also the first time a Control is created on any other thread as well. This should serve to achieve our goal of determining where Controls are being created on a per thread basis, while limiting the amount of unnecessary output.

To do this, start your Windows Forms application and attach Windbg. Once attached, make sure your symbol path is set as appropriate and reload symbols. You can set it to the public Microsoft symbol server, or use your own internal symbol path.

0:000>.sympath srv*msdl.microsoft.com/download/symbols
Symbol search path is: srv*msdl.microsoft.com/download/symbols
Expanded Symbol search path is: srv*msdl.microsoft.com/download/symbols

0:000>.reload

Reloading current modules
.................................

 Load the SOS.dll debugger extension from the folder where the CLR (mscorwks.dll) is located. Note: if debugging .NET 4.0 or later, you would use “clr” instead of “mscorwks” in the command below.

0:000>.loadby sos.dll mscorwks 

Locate the address of the constructor for System.Windows.Forms.Application+MarshalingControl. We do this using !name2ee along with the module name for the DLL (you can use * to search all modules), and the fully qualified method name.

0:000>!name2ee System_Windows_Forms_ni System.Windows.Forms.Application+MarshalingControl..ctor

Module: 7a981000 (System.Windows.Forms.dll)
Token: 0x060017d8
MethodDesc: 7a99cbcc
Name: System.Windows.Forms.Application+MarshalingControl..ctor()
JITTED Code Address: 7ab46fc0

7ab46fc0 is the address we want to place a breakpoint on. This breakpoint should be hit the first time a Control is created on a thread. It will dump the managed callstack to the output window and then continue on with normal execution.

0:000> bp 7ab46fc0 ".echo MarshalingControl creation detected. Callstack follows.;!clrstack;.echo ==============================;gc"

Then run the application and operate under normal conditions to see what output you receive.

0:000> g
MarshalingControl creation detected. Callstack follows.
OS Thread Id: 0x410 (4)
ESP EIP
07b8ed34 7ab46fc0 System.Windows.Forms.Application+MarshalingControl..ctor()
07b8ed38 7ab75b33 System.Windows.Forms.Application+ThreadContext.get_MarshalingControl()
07b8ed68 7ab75713 System.Windows.Forms.WindowsFormsSynchronizationContext..ctor()
07b8ed7c 7ab75615 System.Windows.Forms.WindowsFormsSynchronizationContext.InstallIfNeeded()
07b8eda8 7ab746e8 System.Windows.Forms.Control..ctor(Boolean)
07b8ee48 7ab7c3e4 System.Windows.Forms.ScrollableControl..ctor()
07b8ee64 7ab7c2f7 System.Windows.Forms.ContainerControl..ctor()
07b8ee78 7ab743db System.Windows.Forms.Form..ctor()
07b8ee94 005a0478 UserPreferencesHang.ProgressForm..ctor()
07b8eea0 005d046a UserPreferencesHang.Form1.DisplayProgressForm()
07b8eeb4 005d041b UserPreferencesHang.Form1.backgroundWorker1_DoWork(System.Object, System.ComponentModel.DoWorkEventArgs)
07b8eec8 7a84d3ed System.ComponentModel.BackgroundWorker.OnDoWork(System.ComponentModel.DoWorkEventArgs)
07b8eee0 7a84d14e System.ComponentModel.BackgroundWorker.WorkerThreadStart(System.Object)
07b8f308 797e1b4c [HelperMethodFrame_PROTECTOBJ: 07b8f308] System.Runtime.Remoting.Messaging.StackBuilderSink._PrivateProcessMessage(IntPtr, System.Object[], System.Object, Int32, Boolean, System.Object[] ByRef)
07b8f44c 78af95c3 System.Runtime.Remoting.Messaging.StackBuilderSink.PrivateProcessMessage(System.RuntimeMethodHandle, System.Object[], System.Object, Int32, Boolean, System.Object[] ByRef)
07b8f470 790e273a System.Runtime.Remoting.Messaging.StackBuilderSink.AsyncProcessMessage(System.Runtime.Remoting.Messaging.IMessage, System.Runtime.Remoting.Messaging.IMessageSink)
07b8f4d0 790f646a System.Runtime.Remoting.Proxies.AgileAsyncWorkerItem.DoAsyncCall()
07b8f4dc 790f64a0 System.Runtime.Remoting.Proxies.AgileAsyncWorkerItem.ThreadPoolCallBack(System.Object)
07b8f4e4 78b4a18f System.Threading._ThreadPoolWaitCallback.WaitCallback_Context(System.Object)
07b8f4ec 78b604bf System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
07b8f504 78b4a6f3 System.Threading._ThreadPoolWaitCallback.PerformWaitCallbackInternal(System.Threading._ThreadPoolWaitCallback)
07b8f518 78b4a589 System.Threading._ThreadPoolWaitCallback.PerformWaitCallback(System.Object)
07b8f6a8 797e1b4c [GCFrame: 07b8f6a8]

The output above shows where this sample application was using the BackgroundWorker component, and handling it's DoWork event. The handler for this is fired on an MTA thread in the .NET Threadpool. We see where the BackgroundWorker's event handler called into some custom code in UserPreferencesHang.Form1.DisplayProgressForm(), which then created an instance of a custom Windows Form. From this output, now we know where to fix our code to prevent it from creating the Form on this background thread. In this case, the fix would be to use Control.Invoke (or BeginInvoke) to marshal the call of the DisplayProgressForm method over to the UI thread.

Final Thoughts

We have explained how .NET Windows Forms applications can become unresponsive when handling the Microsoft.Win32.SystemEvents.UserPreferenceChanged event. We have demonstrated how to debug an application already in this state, and more importantly, how to use the debugger to track the creation of Windows Forms on other threads. This should go a long way to helping you diagnose and correct any applications that are suffering from this condition.

Happy Hunting!