Найти целевой поток вызова в дампе памяти с помощью Windbg
Фон
Клиент сообщил о зависании в приложении C#. У меня дамп памяти в момент зависания приложения. Дамп памяти показывает основной поток пользовательского интерфейса, отображающий форму выполнения и несколько фоновых потоков. Один из фоновых потоков пытается Control.Invoke вернуться к основному потоку, чтобы обновить форму. Стек CLR выглядит так:
System.Threading.WaitHandle.WaitOneNative(Microsoft.Win32.SafeHandles.SafeWaitHandle, UInt32, Boolean, Boolean)
System.Threading.WaitHandle.WaitOne(Int64, Boolean)
System.Threading.WaitHandle.WaitOne(Int32, Boolean)
System.Windows.Forms.Control.WaitForWaitHandle(System.Threading.WaitHandle)
System.Windows.Forms.Control.MarshaledInvoke(System.Windows.Forms.Control, System.Delegate, System.Object[], Boolean)
System.Windows.Forms.Control.Invoke(System.Delegate, System.Object[])
profdata.com.Library.frmAsyncExec.SetMessageText(System.String)
profdata.com.Library.frmAsyncExec.SetMessage(System.String, System.String)
profdata.com.Library.frmAsyncExec.DoAsyncProcess(System.Object)
System.Threading.ThreadHelper.ThreadStart_Context(System.Object)
System.Threading.ExecutionContext.runTryCode(System.Object)
System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(TryCode, CleanupCode, System.Object)
System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
System.Threading.ThreadHelper.ThreadStart(System.Object)
Поток пользовательского интерфейса находится в модальном цикле и ожидает сообщения:
System.Windows.Forms.UnsafeNativeMethods.WaitMessage()
System.Windows.Forms.Application+ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(Int32, Int32, Int32)
System.Windows.Forms.Application+ThreadContext.RunMessageLoopInner(Int32, System.Windows.Forms.ApplicationContext)
System.Windows.Forms.Application+ThreadContext.RunMessageLoop(Int32, System.Windows.Forms.ApplicationContext)
System.Windows.Forms.Application.RunDialog(System.Windows.Forms.Form)
System.Windows.Forms.Form.ShowDialog(System.Windows.Forms.IWin32Window)
profdata.com.Library.frmAppletBaseForm.ShowDialog(System.Windows.Forms.IWin32Window)
profdata.com.Library.frmAsyncExec.ExecuteProcess(System.Windows.Forms.IWin32Window)
Я убедился, что единственное сообщение в очереди сообщений (которое я вижу) - это сообщение рисования. Сообщение 15 соответствует WM_PAINT:
0:000> !dso
OS Thread Id: 0x126c (0)
ESP/REG Object Name
ebx 01a688d0 System.Windows.Forms.Application+ThreadContext
esi 01a6eb9c System.Windows.Forms.Application+ComponentManager+ComponentHashtableEntry
edi 1b0c05e0 System.Collections.Hashtable+HashtableEnumerator
0036e244 1b08430c System.Windows.Forms.NativeMethods+MSG[]
0:000> !da 1b08430c
Name: System.Windows.Forms.NativeMethods+MSG[]
MethodTable: 67e0592c
EEClass: 67be89b8
Size: 40(0x28) bytes
Array: Rank 1, Number of elements 1, Type VALUETYPE
Element Methodtable: 67e059dc
0:000> !dumpvc 67e059dc 1b084314
Name: System.Windows.Forms.NativeMethods+MSG
MethodTable 67e059dc
EEClass: 67bbd880
Size: 36(0x24) 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
691b35f0 4002ba0 0 System.IntPtr 1 instance b02ee hwnd
691b2f74 4002ba1 4 System.Int32 1 instance 15 message
691b35f0 4002ba2 8 System.IntPtr 1 instance 0 wParam
691b35f0 4002ba3 c System.IntPtr 1 instance 0 lParam
691b2f74 4002ba4 10 System.Int32 1 instance 264314085 time
691b2f74 4002ba5 14 System.Int32 1 instance 188 pt_x
691b2f74 4002ba6 18 System.Int32 1 instance 386 pt_y
Я знаю, что Control.MarshaledInvoke реализован как PostMessage известного сообщения:
private object MarshaledInvoke(Control caller, Delegate method, object[] args, bool synchronous)
{
...
System.Windows.Forms.UnsafeNativeMethods.PostMessage(new HandleRef(this, this.Handle), threadCallbackMessage, IntPtr.Zero, IntPtr.Zero);
...
}
MSDN говорит об PostMessage:
Помещает (публикует) сообщение в очередь сообщений, связанную с потоком, который создал указанное окно, и возвращает его, не дожидаясь, пока поток обработает сообщение.
Я подозреваю, что это классический случай фонового потока, обращающегося к объекту пользовательского интерфейса, который, как известно, вызывает зависание приложения. Я хорошо знаю, что это плохо.
Поскольку есть несколько фоновых потоков, я бы хотел определить, какой фоновый поток испортил.
Вопрос
Можно ли определить поток назначения для вызова Control.Invoke, используя информацию в дампе памяти?
Работа так далеко
Я получил дамп объектов стека фонового потока:
0:000> ~20s
0:020> !dso
OS Thread Id: 0x5d8 (20)
ESP/REG Object Name
ecx 01a2b2d0 System.Runtime.Remoting.Contexts.Context
0c42e49c 1b083e20 System.Threading.ManualResetEvent
0c42e524 01a2006c System.Collections.Hashtable
0c42e560 1b083e38 Microsoft.Win32.SafeHandles.SafeWaitHandle
0c42e5fc 1b044128 System.Windows.Forms.PropertyStore
0c42e608 1b060b68 System.Collections.Queue
0c42e610 1b083de0 System.Windows.Forms.Control+ThreadMethodEntry
0c42e65c 1b043b30 profdata.com.DailyReporting.frmDRAsyncExec
0c42e66c 1b083cb0 System.Object[] (System.Object[])
0c42e670 1b083c90 profdata.com.Library.frmAsyncExec+SetMessageDelegate
0c42e674 1b083cc4 System.Windows.Forms.Control+MultithreadSafeCallScope
0c42e694 1b043b30 profdata.com.DailyReporting.frmDRAsyncExec
Я пытался зацикливаться на ThreadMethodEntry и объекте Context, но я не уверен, что я ищу:
0:020> !do 1b083de0
Name: System.Windows.Forms.Control+ThreadMethodEntry
MethodTable: 67e05380
EEClass: 67be8454
Size: 52(0x34) 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
67dff750 40011b9 4 ...ows.Forms.Control 0 instance 1b043b30 caller
67dff750 40011ba 8 ...ows.Forms.Control 0 instance 1b043b30 marshaler
691b118c 40011bb c System.Delegate 0 instance 1b083c90 method
691844f8 40011bc 10 System.Object[] 0 instance 1b083cb0 args
691b0944 40011bd 14 System.Object 0 instance 00000000 retVal
691b0ebc 40011be 18 System.Exception 0 instance 00000000 exception
691847f4 40011bf 2c System.Boolean 1 instance 1 synchronous
691847f4 40011c0 2d System.Boolean 1 instance 0 isCompleted
69197b54 40011c1 1c ....ManualResetEvent 0 instance 1b083e20 resetEvent
691b0944 40011c2 20 System.Object 0 instance 1b083e14 invokeSyncObject
691ada1c 40011c3 24 ....ExecutionContext 0 instance 1b083cd0 executionContext
691aa00c 40011c4 28 ...ronizationContext 0 instance 00000000 syncContext
0:020> !do 01a2b2d0
Name: System.Runtime.Remoting.Contexts.Context
MethodTable: 691a210c
EEClass: 68fcf620
Size: 60(0x3c) bytes
(C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
Fields:
MT Field Offset Type VT Attr Value Name
691844f8 4001f60 4 System.Object[] 0 instance 01a2b3a0 _ctxProps
691a219c 4001f61 8 ...micPropertyHolder 0 instance 00000000 _dphCtx
691a75b4 4001f62 c ...em.LocalDataStore 0 instance 00000000 _localDataStore
69196f18 4001f63 10 ...ging.IMessageSink 0 instance 00000000 _serverContextChain
69196f18 4001f64 14 ...ging.IMessageSink 0 instance 00000000 _clientContextChain
691b1508 4001f65 18 System.AppDomain 0 instance 01a01298 _appDomain
691844f8 4001f66 1c System.Object[] 0 instance 00000000 _ctxStatics
691b35f0 4001f67 20 System.IntPtr 1 instance 551520 _internalContext
691b2f74 4001f68 24 System.Int32 1 instance 0 _ctxID
691b2f74 4001f69 28 System.Int32 1 instance 3 _ctxFlags
691b2f74 4001f6a 2c System.Int32 1 instance 1 _numCtxProps
691b2f74 4001f6b 30 System.Int32 1 instance 0 _ctxStaticsCurrentBucket
691b2f74 4001f6c 34 System.Int32 1 instance 0 _ctxStaticsFreeIndex
691a219c 4001f6d 654 ...micPropertyHolder 0 shared static _dphGlobal
>> Domain:Value 00557ff8:01a2b068 <<
69189944 4001f6e 658 ...LocalDataStoreMgr 0 shared static _localDataStoreMgr
>> Domain:Value 00557ff8:01a2b07c <<
691b2f74 4001f6f b40 System.Int32 1 shared static _ctxIDCounter
>> Domain:Value 00557ff8:0 <<
Или, может быть, есть более нативный подход, который включает в себя поиск ассоциации дескриптора окна с конкретным потоком, но если это так, я не знаю, как это сделать.
1 ответ
Вам необходимо вывести в свой ThreadMethodEntry делегат (поле метода), который выполняется в потоке пользовательского интерфейса. Остальные члены присутствуют только для того, чтобы поток пользовательского интерфейса знал, какому элементу управления отправлять обратный вызов и в каком контексте синхронизации он выполняется.
Есть также некоторые флаги, чтобы проверить, нужен ли вообще вызов, и поле исключения, если вызов не удался, который может быть передан обратно вызывающей стороне, если это было сделано с помощью вызова Invoke. Вы можете спокойно игнорировать другие вещи там.
Более подробную информацию о том, как узнать, какой метод был вызван, когда у вас есть только экземпляр делегата, вы найдете здесь: http://geekswithblogs.net/akraus1/archive/2012/05/20/149699.aspx
Поскольку ваш обратный вызов даже не выполняется, вам следует взглянуть и на другие потоки. Такие загадочные зависания часто происходят в связи с сообщением окна WM_SETTING_CHANGE, в котором поток, не являющийся пользовательским интерфейсом, пытается вызвать ваши элементы управления (см. http://ikriv.com/dev/dotnet/MysteriousHang.html).