【问题标题】:WPF application hang by PropertyChangedEventManager in concurrent environmentsWPF 应用程序在并发环境中被 PropertyChangedEventManager 挂起
【发布时间】:2014-07-26 03:16:42
【问题描述】:

我正在开发一个复杂的 WPF 应用程序,它会在生产环境中挂起几天。除了 GUI 线程之外,还有一个线程将数据填充到模型绑定到网格并触发 INotifyPropertyChanged.PropertyChanged 事件。我编写了一个脚本来将 MDbg 附加到挂起的进程并转储线程的当前堆栈跟踪。找到死锁的原因有很大帮助,但这次没有帮助。

正在更新模型的线程在获取ReadLock时停止:

Thread [#:8]
*0. WindowsBase.dll#0!MS.Internal.ReaderWriterLockWrapper.get_ReadLock()  (source line information unavailable)
 1. WindowsBase.dll#0!System.ComponentModel.PropertyChangedEventManager.OnPropertyChanged(sender=<...>, args=System.ComponentModel.PropertyChangedEventArgs)  (source line information unavailable)
 2. ( ... firing PropertyChanged event ... )

同样的事情也发生在 GUI 线程上:

Thread [#:0]
*0. WindowsBase.dll#0!MS.Internal.ReaderWriterLockWrapper.get_ReadLock()  (source line information unavailable)
 1. WindowsBase.dll#0!System.ComponentModel.PropertyChangedEventManager.OnPropertyChanged(sender=MyCompany.Windows.ViewModel.Window.WindowViewModel, args=System.ComponentModel.PropertyChangedEventArgs)  (source line information unavailable)
 2. MyCompany.Windows.Contracts.dll#0!MyCompany.Windows.ViewModel.Model.ViewModelItemBase.NotifyPropertyChanged(propertyName="IsActive")  (source line information unavailable)
 3. MyCompany.Windows.Contracts.dll#0!MyCompany.Windows.ViewModel.Window.WindowViewModel.set_IsActive(value=True)  (source line information unavailable)
 4. MyCompany.Windows.dll#0!MyCompany.Windows.ViewModel.Window.IsActiveBinding.OnWindowIsActiveChanged(sender=MyCompany.Xpf.Views.XpfRibbonShell.XpfRibbonShellView, e=System.EventArgs)  (source line information unavailable)
 5. WindowsBase.dll#0!MS.Internal.ComponentModel.PropertyChangeTracker.OnPropertyInvalidation(d=<N/A>, args=<N/A>)  (source line information unavailable)
 6. WindowsBase.dll#0!System.Windows.DependentList.InvalidateDependents(source=<N/A>, sourceArgs=<N/A>)  (source line information unavailable)
 7. WindowsBase.dll#0!System.Windows.DependencyObject.NotifyPropertyChange(args=<N/A>)  (source line information unavailable)
 8. WindowsBase.dll#0!System.Windows.DependencyObject.UpdateEffectiveValue(entryIndex=<N/A>, dp=<N/A>, metadata=<N/A>, oldEntry=<N/A>, newEntry=<N/A>, coerceWithDeferredReference=<N/A>, coerceWithCurrentValue=<N/A>, operationType=<N/A>)  (source line information unavailable)
 9. WindowsBase.dll#0!System.Windows.DependencyObject.SetValueCommon(dp=<N/A>, value=<N/A>, metadata=<N/A>, coerceWithDeferredReference=<N/A>, coerceWithCurrentValue=<N/A>, operationType=<N/A>, isInternal=<N/A>)  (source line information unavailable)
 10. WindowsBase.dll#0!System.Windows.DependencyObject.SetValue(key=<N/A>, value=<N/A>)  (source line information unavailable)
 11. PresentationFramework.dll#0!System.Windows.Window.HandleActivate(windowActivated=<N/A>)  (source line information unavailable)
 12. PresentationFramework.dll#0!System.Windows.Window.WmActivate(wParam=<N/A>)  (source line information unavailable)
 13. PresentationFramework.dll#0!System.Windows.Window.WindowFilterMessage(hwnd=<N/A>, msg=<N/A>, wParam=<N/A>, lParam=<N/A>, handled=<N/A>)  (source line information unavailable)
 14. PresentationCore.dll#0!System.Windows.Interop.HwndSource.PublicHooksFilterMessage(hwnd=<N/A>, msg=<N/A>, wParam=<N/A>, lParam=<N/A>, handled=<N/A>)  (source line information unavailable)
 15. WindowsBase.dll#0!MS.Win32.HwndWrapper.WndProc(hwnd=<N/A>, msg=<N/A>, wParam=<N/A>, lParam=<N/A>, handled=<N/A>)  (source line information unavailable)
 16. WindowsBase.dll#0!MS.Win32.HwndSubclass.DispatcherCallbackOperation(o=<N/A>)  (source line information unavailable)
 17. WindowsBase.dll#0!System.Windows.Threading.ExceptionWrapper.InternalRealCall(callback=<N/A>, args=<N/A>, numArgs=<N/A>)  (source line information unavailable)
 18. WindowsBase.dll#0!MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(source=System.Windows.Threading.Dispatcher, method=<N/A>, args=<N/A>, numArgs=<N/A>, catchHandler=<null>)  (source line information unavailable)
 19. WindowsBase.dll#0!System.Windows.Threading.Dispatcher.WrappedInvoke(callback=<N/A>, args=<N/A>, numArgs=<N/A>, catchHandler=<N/A>)  (source line information unavailable)
 20. WindowsBase.dll#0!System.Windows.Threading.Dispatcher.InvokeImpl(priority=<N/A>, timeout=<N/A>, method=<N/A>, args=<N/A>, numArgs=<N/A>)  (source line information unavailable)
 21. WindowsBase.dll#0!MS.Win32.HwndSubclass.SubclassWndProc(hwnd=<N/A>, msg=<N/A>, wParam=<N/A>, lParam=<N/A>)  (source line information unavailable)
    [IL Method without Metadata]
 22. WindowsBase.dll#0!MS.Internal.ReaderWriterLockWrapper.get_ReadLock()  (source line information unavailable)
 23. WindowsBase.dll#0!System.ComponentModel.PropertyChangedEventManager.OnPropertyChanged(sender=MyCompany.Windows.ViewModel.Window.WindowViewModel, args=System.ComponentModel.PropertyChangedEventArgs)  (source line information unavailable)
 24. MyCompany.Windows.Contracts.dll#0!MyCompany.Windows.ViewModel.Model.ViewModelItemBase.NotifyPropertyChanged(propertyName="IsActive")  (source line information unavailable)
 25. MyCompany.Windows.Contracts.dll#0!MyCompany.Windows.ViewModel.Window.WindowViewModel.set_IsActive(value=False)  (source line information unavailable)
 26. MyCompany.Windows.dll#0!MyCompany.Windows.ViewModel.Window.IsActiveBinding.OnWindowIsActiveChanged(sender=MyCompany.Xpf.Views.XpfRibbonShell.XpfRibbonShellView, e=System.EventArgs)  (source line information unavailable)
 27. WindowsBase.dll#0!MS.Internal.ComponentModel.PropertyChangeTracker.OnPropertyInvalidation(d=<N/A>, args=<N/A>)  (source line information unavailable)
 28. WindowsBase.dll#0!System.Windows.DependentList.InvalidateDependents(source=<N/A>, sourceArgs=<N/A>)  (source line information unavailable)
 29. WindowsBase.dll#0!System.Windows.DependencyObject.NotifyPropertyChange(args=<N/A>)  (source line information unavailable)
 30. WindowsBase.dll#0!System.Windows.DependencyObject.UpdateEffectiveValue(entryIndex=<N/A>, dp=<N/A>, metadata=<N/A>, oldEntry=<N/A>, newEntry=<N/A>, coerceWithDeferredReference=<N/A>, coerceWithCurrentValue=<N/A>, operationType=<N/A>)  (source line information unavailable)
 31. WindowsBase.dll#0!System.Windows.DependencyObject.SetValueCommon(dp=<N/A>, value=<N/A>, metadata=<N/A>, coerceWithDeferredReference=<N/A>, coerceWithCurrentValue=<N/A>, operationType=<N/A>, isInternal=<N/A>)  (source line information unavailable)
 32. WindowsBase.dll#0!System.Windows.DependencyObject.SetValue(key=<N/A>, value=<N/A>)  (source line information unavailable)
 33. PresentationFramework.dll#0!System.Windows.Window.HandleActivate(windowActivated=<N/A>)  (source line information unavailable)
 34. PresentationFramework.dll#0!System.Windows.Window.WmActivate(wParam=<N/A>)  (source line information unavailable)
 35. PresentationFramework.dll#0!System.Windows.Window.WindowFilterMessage(hwnd=<N/A>, msg=<N/A>, wParam=<N/A>, lParam=<N/A>, handled=<N/A>)  (source line information unavailable)
 36. PresentationCore.dll#0!System.Windows.Interop.HwndSource.PublicHooksFilterMessage(hwnd=<N/A>, msg=<N/A>, wParam=<N/A>, lParam=<N/A>, handled=<N/A>)  (source line information unavailable)
 37. WindowsBase.dll#0!MS.Win32.HwndWrapper.WndProc(hwnd=<N/A>, msg=<N/A>, wParam=<N/A>, lParam=<N/A>, handled=<N/A>)  (source line information unavailable)
 38. WindowsBase.dll#0!MS.Win32.HwndSubclass.DispatcherCallbackOperation(o=<N/A>)  (source line information unavailable)
 39. WindowsBase.dll#0!System.Windows.Threading.ExceptionWrapper.InternalRealCall(callback=<N/A>, args=<N/A>, numArgs=<N/A>)  (source line information unavailable)
 40. WindowsBase.dll#0!MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(source=System.Windows.Threading.Dispatcher, method=<N/A>, args=<N/A>, numArgs=<N/A>, catchHandler=<null>)  (source line information unavailable)
 41. WindowsBase.dll#0!System.Windows.Threading.Dispatcher.WrappedInvoke(callback=<N/A>, args=<N/A>, numArgs=<N/A>, catchHandler=<N/A>)  (source line information unavailable)
 42. WindowsBase.dll#0!System.Windows.Threading.Dispatcher.InvokeImpl(priority=<N/A>, timeout=<N/A>, method=<N/A>, args=<N/A>, numArgs=<N/A>)  (source line information unavailable)
 43. WindowsBase.dll#0!MS.Win32.HwndSubclass.SubclassWndProc(hwnd=<N/A>, msg=<N/A>, wParam=<N/A>, lParam=<N/A>)  (source line information unavailable)
    [IL Method without Metadata]
 44. WindowsBase.dll#0!MS.Internal.ReaderWriterLockWrapper.get_ReadLock()  (source line information unavailable)
 45. WindowsBase.dll#0!System.ComponentModel.PropertyChangedEventManager.OnPropertyChanged(sender=MyCompany.Windows.ViewModel.Window.WindowViewModel, args=System.ComponentModel.PropertyChangedEventArgs)  (source line information unavailable)
 46. MyCompany.Windows.Contracts.dll#0!MyCompany.Windows.ViewModel.Model.ViewModelItemBase.NotifyPropertyChanged(propertyName="IsActive")  (source line information unavailable)
 47. MyCompany.Windows.Contracts.dll#0!MyCompany.Windows.ViewModel.Window.WindowViewModel.set_IsActive(value=True)  (source line information unavailable)
 48. MyCompany.Windows.dll#0!MyCompany.Windows.ViewModel.Window.IsActiveBinding.OnWindowIsActiveChanged(sender=MyCompany.Xpf.Views.XpfRibbonShell.XpfRibbonShellView, e=System.EventArgs)  (source line information unavailable)
 49. WindowsBase.dll#0!MS.Internal.ComponentModel.PropertyChangeTracker.OnPropertyInvalidation(d=<N/A>, args=<N/A>)  (source line information unavailable)
 50. WindowsBase.dll#0!System.Windows.DependentList.InvalidateDependents(source=<N/A>, sourceArgs=<N/A>)  (source line information unavailable)
 51. WindowsBase.dll#0!System.Windows.DependencyObject.NotifyPropertyChange(args=<N/A>)  (source line information unavailable)
 52. WindowsBase.dll#0!System.Windows.DependencyObject.UpdateEffectiveValue(entryIndex=<N/A>, dp=<N/A>, metadata=<N/A>, oldEntry=<N/A>, newEntry=<N/A>, coerceWithDeferredReference=<N/A>, coerceWithCurrentValue=<N/A>, operationType=<N/A>)  (source line information unavailable)
 53. WindowsBase.dll#0!System.Windows.DependencyObject.SetValueCommon(dp=<N/A>, value=<N/A>, metadata=<N/A>, coerceWithDeferredReference=<N/A>, coerceWithCurrentValue=<N/A>, operationType=<N/A>, isInternal=<N/A>)  (source line information unavailable)
 54. WindowsBase.dll#0!System.Windows.DependencyObject.SetValue(key=<N/A>, value=<N/A>)  (source line information unavailable)
 55. PresentationFramework.dll#0!System.Windows.Window.HandleActivate(windowActivated=<N/A>)  (source line information unavailable)
 56. PresentationFramework.dll#0!System.Windows.Window.WmActivate(wParam=<N/A>)  (source line information unavailable)
 57. PresentationFramework.dll#0!System.Windows.Window.WindowFilterMessage(hwnd=<N/A>, msg=<N/A>, wParam=<N/A>, lParam=<N/A>, handled=<N/A>)  (source line information unavailable)
 58. PresentationCore.dll#0!System.Windows.Interop.HwndSource.PublicHooksFilterMessage(hwnd=<N/A>, msg=<N/A>, wParam=<N/A>, lParam=<N/A>, handled=<N/A>)  (source line information unavailable)
 59. WindowsBase.dll#0!MS.Win32.HwndWrapper.WndProc(hwnd=<N/A>, msg=<N/A>, wParam=<N/A>, lParam=<N/A>, handled=<N/A>)  (source line information unavailable)
 60. WindowsBase.dll#0!MS.Win32.HwndSubclass.DispatcherCallbackOperation(o=<N/A>)  (source line information unavailable)
 61. WindowsBase.dll#0!System.Windows.Threading.ExceptionWrapper.InternalRealCall(callback=<N/A>, args=<N/A>, numArgs=<N/A>)  (source line information unavailable)
 62. WindowsBase.dll#0!MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(source=System.Windows.Threading.Dispatcher, method=<N/A>, args=<N/A>, numArgs=<N/A>, catchHandler=<null>)  (source line information unavailable)
 63. WindowsBase.dll#0!System.Windows.Threading.Dispatcher.WrappedInvoke(callback=<N/A>, args=<N/A>, numArgs=<N/A>, catchHandler=<N/A>)  (source line information unavailable)
 64. WindowsBase.dll#0!System.Windows.Threading.Dispatcher.InvokeImpl(priority=<N/A>, timeout=<N/A>, method=<N/A>, args=<N/A>, numArgs=<N/A>)  (source line information unavailable)
 65. WindowsBase.dll#0!MS.Win32.HwndSubclass.SubclassWndProc(hwnd=<N/A>, msg=<N/A>, wParam=<N/A>, lParam=<N/A>)  (source line information unavailable)
    [IL Method without Metadata]
 66. WindowsBase.dll#0!System.ComponentModel.PropertyChangedEventManager.PrivateAddListener(source=<N/A>, listener=<N/A>, propertyName=<N/A>)  (source line information unavailable)
 67. PresentationFramework.dll#0!MS.Internal.Data.PropertyPathWorker.ReplaceItem(k=<N/A>, newO=<N/A>, parent=<N/A>)  (source line information unavailable)
 68. PresentationFramework.dll#0!MS.Internal.Data.PropertyPathWorker.UpdateSourceValueState(k=<N/A>, collectionView=<N/A>, newValue=<N/A>, isASubPropertyChange=<N/A>)  (source line information unavailable)
 69. PresentationFramework.dll#0!MS.Internal.Data.ClrBindingWorker.AttachDataItem()  (source line information unavailable)
 70. PresentationFramework.dll#0!System.Windows.Data.BindingExpression.Activate(item=<N/A>)  (source line information unavailable)
 71. PresentationFramework.dll#0!System.Windows.Data.BindingExpression.AttachToContext(attempt=<N/A>)  (source line information unavailable)
 72. PresentationFramework.dll#0!System.Windows.Data.BindingExpression.MS.Internal.Data.IDataBindEngineClient.AttachToContext(lastChance=<N/A>)  (source line information unavailable)
 73. PresentationFramework.dll#0!MS.Internal.Data.DataBindEngine+Task.Run(lastChance=<N/A>)  (source line information unavailable)
 74. PresentationFramework.dll#0!MS.Internal.Data.DataBindEngine.Run(arg=<N/A>)  (source line information unavailable)
 75. PresentationFramework.dll#0!MS.Internal.Data.DataBindEngine.OnLayoutUpdated(sender=<N/A>, e=<N/A>)  (source line information unavailable)
 76. PresentationCore.dll#0!System.Windows.ContextLayoutManager.fireLayoutUpdateEvent()  (source line information unavailable)
 77. PresentationCore.dll#0!System.Windows.ContextLayoutManager.UpdateLayout()  (source line information unavailable)
 78. PresentationCore.dll#0!System.Windows.ContextLayoutManager.UpdateLayoutCallback(arg=<N/A>)  (source line information unavailable)
 79. PresentationCore.dll#0!System.Windows.Media.MediaContext.FireInvokeOnRenderCallbacks()  (source line information unavailable)
 80. PresentationCore.dll#0!System.Windows.Media.MediaContext.RenderMessageHandlerCore(resizedCompositionTarget=<N/A>)  (source line information unavailable)
 81. PresentationCore.dll#0!System.Windows.Media.MediaContext.RenderMessageHandler(resizedCompositionTarget=<N/A>)  (source line information unavailable)
 82. WindowsBase.dll#0!System.Windows.Threading.ExceptionWrapper.InternalRealCall(callback=<N/A>, args=<N/A>, numArgs=<N/A>)  (source line information unavailable)
 83. WindowsBase.dll#0!MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(source=System.Windows.Threading.Dispatcher, method=<N/A>, args=<N/A>, numArgs=<N/A>, catchHandler=<null>)  (source line information unavailable)
 84. WindowsBase.dll#0!System.Windows.Threading.Dispatcher.WrappedInvoke(callback=<N/A>, args=<N/A>, numArgs=<N/A>, catchHandler=<N/A>)  (source line information unavailable)
 85. WindowsBase.dll#0!System.Windows.Threading.DispatcherOperation.InvokeImpl()  (source line information unavailable)
 86. mscorlib.dll#0!System.Threading.ExecutionContext.runTryCode(userData=<N/A>)  (source line information unavailable)
 87. mscorlib.dll#0!System.Threading.ExecutionContext.Run(executionContext=<N/A>, callback=<N/A>, state=<N/A>, ignoreSyncCtx=<N/A>)  (source line information unavailable)
 88. mscorlib.dll#0!System.Threading.ExecutionContext.Run(executionContext=<N/A>, callback=<N/A>, state=<N/A>)  (source line information unavailable)
 89. WindowsBase.dll#0!System.Windows.Threading.DispatcherOperation.Invoke()  (source line information unavailable)
 90. WindowsBase.dll#0!System.Windows.Threading.Dispatcher.ProcessQueue()  (source line information unavailable)
 91. WindowsBase.dll#0!System.Windows.Threading.Dispatcher.WndProcHook(hwnd=<N/A>, msg=<N/A>, wParam=<N/A>, lParam=<N/A>, handled=<N/A>)  (source line information unavailable)
 92. WindowsBase.dll#0!MS.Win32.HwndWrapper.WndProc(hwnd=<N/A>, msg=<N/A>, wParam=<N/A>, lParam=<N/A>, handled=<N/A>)  (source line information unavailable)
 93. WindowsBase.dll#0!MS.Win32.HwndSubclass.DispatcherCallbackOperation(o=<N/A>)  (source line information unavailable)
 94. WindowsBase.dll#0!System.Windows.Threading.ExceptionWrapper.InternalRealCall(callback=<N/A>, args=<N/A>, numArgs=<N/A>)  (source line information unavailable)
 95. WindowsBase.dll#0!MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(source=System.Windows.Threading.Dispatcher, method=<N/A>, args=<N/A>, numArgs=<N/A>, catchHandler=<null>)  (source line information unavailable)
 96. WindowsBase.dll#0!System.Windows.Threading.Dispatcher.WrappedInvoke(callback=<N/A>, args=<N/A>, numArgs=<N/A>, catchHandler=<N/A>)  (source line information unavailable)
 97. WindowsBase.dll#0!System.Windows.Threading.Dispatcher.InvokeImpl(priority=<N/A>, timeout=<N/A>, method=<N/A>, args=<N/A>, numArgs=<N/A>)  (source line information unavailable)
 98. WindowsBase.dll#0!MS.Win32.HwndSubclass.SubclassWndProc(hwnd=<N/A>, msg=<N/A>, wParam=<N/A>, lParam=<N/A>)  (source line information unavailable)
    [IL Method without Metadata]
    [Internal Frame, 'M-->U']
    [IL Method without Metadata]
 99. WindowsBase.dll#0!System.Windows.Threading.Dispatcher.PushFrameImpl(frame=<N/A>)  (source line information unavailable)
 100. PresentationFramework.dll#0!System.Windows.Application.RunInternal(window=<N/A>)  (source line information unavailable)
 101. PresentationFramework.dll#0!System.Windows.Application.Run()  (source line information unavailable)
 102. MyProgram.exe#0!XamlGeneratedNamespace.GeneratedApplication.Main()  (source line information unavailable)

似乎有人持有 WriteLock 但从未释放 - 但我如何检查谁持有它?我已经粘贴了我在这里得到的整个堆栈跟踪,有人能给我一些关于根本原因的问题,比如HwndSubclass 是什么,以及为什么随着IsActiveWindowState 属性的变化在堆栈跟踪中反复发生?

如果您需要更多信息,请添加 cmets。

【问题讨论】:

  • 你在 IsActive 的 getter 中做什么?它应该尽可能简单(没有锁、没有 IO 操作、没有繁重的计算)。此外,请确保在 UI 线程中触发了 ProppertyChanged 事件。虽然,从 .NET Framework 4.0(或更早版本?)开始,它会自动分派到 UI 线程。
  • @SergiiVashchyshchuk getter 就像自动属性一样简单。确保在 UI 线程中触发 PropertyChanged 事件是我应用的解决方法,但我想知道问题的根本原因,因为从后台线程通知属性更改现在是一种可接受的模式。当前奇怪的事情是为什么当 GUI 线程自己持有写入器锁时,它会被读取器锁阻塞。
  • 它在订阅 PropetyChanged 事件时也使用此 ReadLock。当您将模型绑定到网格时,它会订阅 PropetyChanged 事件。如何将模型集合绑定到网格?什么类型的收藏?你如何填充它?
  • @SergiiVashchyshchuk 它使用 WriteLock 来维护事件处理程序。绑定到网格和更新模型没什么特别的。你可以假设它只是简单的数组和简单的属性集,触发 PropertyChanged 事件。
  • 我希望您确实针对内存问题对其进行了分析,如果没有,那么我建议您进行内存分析,看看是否有一些有趣的问题需要修复。请参阅 Wikipedia 上有关内存泄漏的此示例,该示例演示了电梯软件的类似问题。 en.wikipedia.org/wiki/Memory_leak#An_example_of_memory_leak,这里只提一下软件老化en.wikipedia.org/wiki/Software_aging

标签: c# wpf multithreading inotifypropertychanged propertychanged


【解决方案1】:

在深入研究了几乎所有相关组件的源代码后,我终于解决了这个问题。感谢出色的Reference Source 网站,与 ILSpy 的反编译结果相比,源代码中的 cmets 有很大帮助。

PropertyChangedEventManager 是什么?

PropertyChangedEventManager (source code here) 是一个在并发环境中处理ProperyChanged 事件处理程序和通知的组件。换句话说,它是线程安全的。在内部,它使用ReaderWriterLock 来保持线程安全。更改事件处理程序时将获取写入器锁,并在有PropertyChanged 事件通知时获取读取器锁。

PropertyChangedEventManager 通常由 WPF 控件使用。当我们将视图模型附加/分离到控件时,我们正在添加/删除PropertyChanged 事件处理程序。我一直想知道谁持有阻止读取器锁的写入器锁(get_ReadLock),但实际上是 GUI 线程本身。

是的,这听起来很奇怪,但正如堆栈跟踪所示,它就在 PrivateAddListener (source code here) 内部:

...
[IL Method without Metadata]
 66. WindowsBase.dll#0!System.ComponentModel.PropertyChangedEventManager.PrivateAddListener(source=<N/A>, listener=<N/A>, propertyName=<N/A>)  (source line information unavailable)
 67. PresentationFramework.dll#0!MS.Internal.Data.PropertyPathWorker.ReplaceItem(k=<N/A>, newO=<N/A>, parent=<N/A>)  (source line information unavailable)
...

顺便说一句,我总是被告知我们应该在后台线程的 UI 绑定对象中触发 PropertyChanged 事件,但自从 .NET 4 以来情况并非如此。PropertyChangedEventManager 旨在用于并发环境。只有当模型绑定到 GUI 控件时才会使用独占(写入器)锁,并且可以同时从多个后台线程触发 PropertyChanged 事件。我们不需要手动将所有内容编组到 GUI 线程。

实际上,在后台线程中更新模型是一种非常重要的模式,有时这是唯一可接受的方法。请考虑当我们有多个 GUI/STA 线程来提高应用程序的响应能力时的情况。我们可以将同一个实例绑定到不同 GUI 线程中的控件。当模型更改时,我们根本无法将PropertyChanged 通知编组到其中任何一个中。跨线程通知是不可避免的。

SubclassWndProc 是什么?

HwndSubclass.SubclassWndProc (source code here) 是处理窗口消息的托管代码的入口点。它由本机代码调用,因此我们总能在堆栈跟踪中找到 [IL Method without Metadata][Internal Frame, 'M--&gt;U']

奇怪的是,为什么堆栈跟踪中有几个SubclassWndProc 调用?窗口消息不应该一个接一个地单独处理吗?要回答这个问题,我们需要检查堆栈跟踪中重复出现的方法的代码:

...
 55. PresentationFramework.dll#0!System.Windows.Window.HandleActivate(windowActivated=<N/A>)  (source line information unavailable)
 56. PresentationFramework.dll#0!System.Windows.Window.WmActivate(wParam=<N/A>)  (source line information unavailable)
 57. PresentationFramework.dll#0!System.Windows.Window.WindowFilterMessage(hwnd=<N/A>, msg=<N/A>, wParam=<N/A>, lParam=<N/A>, handled=<N/A>)  (source line information unavailable)
...

从源代码中我注意到这些方法正在处理WM_ACTIVATE 消息(好吧,我们也可以从名称中看出它)。这是一个特殊的消息as decribed in MSDN

发送到正在激活的窗口和正在停用的窗口。 如果窗口使用相同的输入队列,消息是同步发送的,先发送到被去激活的顶层窗口的窗口过程,然后发送到被激活的顶层窗口的窗口过程。如果窗口使用不同的输入队列,消息是异步发送的,所以窗口立即被激活。

由于消息是在GUI线程中同步发送的,所以会立即处理,不会完成当前的消息。这就是我们可以在堆栈跟踪中找到递归调用的原因。

这也解释了为什么WindowViewMode.IsActive被设置了多次:

  1. 在 #47 - WindowViewModel.set_IsActive(value=True)
  2. 在 #25 - WindowViewModel.set_IsActive(value=False)
  3. 在#3 - WindowViewModel.set_IsActive(value=True)

因为它将被“停用”并再次“激活”。

根本原因及解决办法

WindowViewModel 中,我们有一个IsActive 属性,它与Window.IsActive 属性同步。请注意,它不是双向绑定,因为该属性是只读的。当窗口被激活时,WindowViewModel.IsActive 属性将被设置并触发PropertyChanged 事件。由于WPF控件已经与视图模型挂钩,所以内部逻辑被执行。

我不清楚详细的逻辑是什么(它是[IL Method without Metadata]),但不幸的是它会产生一个新的WM_ACTIVATE 消息。这种情况一次又一次地发生,最终停止 GUI 线程。

确定我们没有在绑定中使用WindowViewModel.IsActive 后,我将其更改为IsActive() 方法。我们不需要触发PropertyChanged 事件,因为它不再是一个属性。

我还留下了一条评论说,如果我们确实需要IsActive 作为属性,我们需要确保PropertyChanged 事件在Dispatcher.BeginInvoke 内部触发,即使它已经在GUI 线程中。我们需要确保异步生成下一条WM_ACTIVATE 消息。

有一件事我无法解释

但我仍然无法解释为什么ReaderWriterLock 会在我们第三次或第四次获取读取器锁时阻塞。我确实认为我们有更深层次的递归PropertyChanged 通知,因此读取器锁定的获取次数将比当前情况更多。但是每次我们遇到这个问题时,我们总是在堆栈跟踪中拥有IsActive 属性。

ReaderWriterLock 或 WPF 甚至操作系统中是否有任何特殊保护?

【讨论】:

  • 根据 MSDN ReaderWriterLock 有一个有趣的实现: 1. 一个线程可以持有一个读取器锁或一个写入器锁,但不能同时持有两者 2. ReaderWriterLock 在一组读取器和一个写入器之间交替3. 当写入队列中的线程正在等待释放活动的读取锁时,请求新读取锁的线程会在读取队列中累积。根据调用堆栈,我们应该在 #66 处获得写入锁,并且很可能试图在堆栈顶部的同一 PropertyChangedEventManager 上获得 ReadLock...
  • @Woodman 同一个线程在获得写入者锁后可以获得读取者锁。我们可以通过简单的代码来验证。从堆栈跟踪来看,前两个 get_ReadLock 很好,但它停在第三个。
  • 如果你想在当前拥有 Writer 锁的情况下获得 Reader 锁,你应该调用DowngradeFromWriterLock;或致电UpgradeToWriterLock,如果您有读卡器锁并且想要获得写卡器锁并且当前有读卡器锁
  • @PeterRitchie UpgradeToWriterLock 用于释放读取器锁以获取写入器锁。它将返回一个LockCookie 以传递给DowngradeToWriterLock。当您获得写入器锁后,您可以拨打AcquireReaderLock,您仍然拥有写入器锁。
  • @JeffreyZhao 不,ReaderWriterLock 的文档非常明确:“线程可以持有读取器锁或写入器锁,但不能同时持有两者”参见msdn.microsoft.com/en-us/library/…
【解决方案2】:

除了 GUI 线程之外,还有一个线程填充数据到模型绑定到网格并触发 INotifyPropertyChanged.PropertyChanged 事件。

不要那样做。没有从后台线程更新 UI 绑定对象的安全方法。充其量它可能会工作一段时间,然后无缘无故地再次崩溃。

如果您正在进行 I/O 绑定调用,请不要使用后台线程。而是使用 async/await 语法来避免阻塞并处理 UI 线程上的所有内容。

如果您受 CPU 限制,则需要将更新编组回 UI 线程。我会分批而不是按对象执行此操作,因为线程编组可能很昂贵。

【讨论】:

  • 我通常喜欢使用 Rx.Net 对一批对象执行 CPU 密集型工作,然后编组到 UI 线程。它有很多方法很有帮助。
  • 我不同意你的看法。理论上,进程中可能有多个 GUI/STA 线程,并且相同的模型可以绑定到不同 GUI 线程中的多个 GUI 组件。从 .NET 4.0 开始,从后台线程触发 PropertyChanged 事件是一种正常模式。 PropertyChangedEventManager 很好地处理了跨线程通知。将所有内容(需要批量)编组到 GUI 中会带来复杂性,通常也会破坏简单的设计。
【解决方案3】:

看起来很可疑,在同一个调用堆栈中,您正在更改 IsActive 属性的值 3 次:

  1. 在 #47 - WindowViewModel.set_IsActive(value=True)
  2. 在 #25 - WindowViewModel.set_IsActive(value=False)
  3. 在 #3 - WindowViewModel.set_IsActive(value=True)

所有 3 次 setter 分别作为 #54、#32 和 #10 中的 Dependency 属性更改的结果而被调用。 您能否检查您的来源并解释两件事:

  1. 如何首先更改 WindowViewModel.IsActive 的值为 True 会导致随后将相同的属性更改为 False(我可以假设 False 值转到 WindowViewModel 类的另一个实例,但我不确定,没有看到来源)
  2. 以及最重要的如何将 False 值分配给 IsActive 属性进而导致将 True 值分配给同一属性?

这就是为什么我假设您已经对 IsActive 属性使用了 TwoWay 绑定,并建议在我的评论中将其替换为 OneWay/OneWayToSource 绑定,因为这样的更改将消除此处的 UI 和 ViewModel 层之间可能存在的干扰.

【讨论】:

  • 实际上是从Window.IsActiveWindowView.IsActive 的单向绑定。我们无法为IsActive 构建双向绑定,因为它是只读属性。顺便说一句,我已经解决了这个问题并自己回答了这个问题。感谢您的帮助。
猜你喜欢
  • 2011-02-10
  • 2014-01-13
  • 2013-03-04
  • 2011-03-13
  • 1970-01-01
  • 2017-12-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多