WPF应用在并发环境下被PropertyChangedEventManager卡住了

9
我正在处理一个复杂的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以及为什么它会在堆栈跟踪中反复出现并改变IsActive和WindowState属性?
如果需要更多信息,请添加注释。

在 IsActive 的 getter 中你做了什么?它应该尽可能简单(没有锁,没有 IO 操作,没有重量级计算)。此外,请确保 PropertyChanged 事件在 UI 线程中触发。虽然从 .NET Framework 4.0 开始(或更早?)它会自动分派到 UI 线程。 - Sergii Vashchyshchuk
@SergiiVashchyshchuk getter 和自动属性一样简单。确保在 UI 线程触发 PropertyChanged 事件是我应用的解决方法,但我想知道这个问题的根本原因,因为现在从后台线程通知属性更改是可以接受的模式。当前奇怪的事情是,当GUI线程自己持有写锁时,它为什么会被读取锁阻塞。 - Jeffrey Zhao
它还在订阅PropertyChanged事件时使用此ReadLock。当您将模型绑定到网格时,它会订阅PropertyChanged事件。您如何将模型集合绑定到网格?集合的类型是什么?您如何填充它? - Sergii Vashchyshchuk
@SergiiVashchyshchuk 它使用 WriteLock 来维护事件处理程序。将其绑定到网格并更新模型并没有什么特别之处。您可以假设它只是简单的数组,并使用触发 PropertyChanged 事件的简单属性集。 - Jeffrey Zhao
@Zache 谢谢。我也正在检查代码。并发不是根本原因。我认为我已经解决了这个问题,并在进行更多验证后回答自己的问题。 - Jeffrey Zhao
显示剩余9条评论
3个回答

4
I've finally fixed the issue after digging into the source code of almost all the related components. Thanks to the fantastic Reference Source website, the comments in the source code helped a lot compared to the decompiled result from ILSpy.
What's PropertyChangedEventManager? PropertyChangedEventManager (source code here) 是一个处理ProperyChanged事件处理程序和通知的组件,它在并发环境中运行。换句话说,它是线程安全的。内部使用ReaderWriterLock来保持线程安全。当事件处理程序正在更改时,会获取写入锁,而在存在PropertyChanged事件通知时,则会获取读取锁。

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

是的,听起来很奇怪,但它只存在于PrivateAddListener(源代码在这里)中,正如堆栈跟踪所示:

...
[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源代码在此处)是处理窗口消息的托管代码的入口点。它由本地代码调用,因此我们总是可以在堆栈跟踪中找到[IL Method without Metadata][Internal Frame, 'M-->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消息(好吧,我们也可以从名称中看出)。它是一个特殊的消息如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 中,我们有一个与 Window.IsActive 属性同步的 IsActive 属性。请注意,它不是双向绑定,因为该属性是只读的。当窗口被激活时, WindowViewModel.IsActive 属性将被设置并触发 PropertyChanged 事件。由于WPF控件已经与视图模型连接,所以内部逻辑被执行。

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

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

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

我无法解释的一件事

但我仍然无法解释为什么在第三或第四次获取读取器锁时 ReaderWriterLock 会阻塞。我确实认为我们有更深层次的递归 PropertyChanged 通知,因此读取器锁会被获取多次。但每次遇到此问题时,堆栈跟踪中都会出现 IsActive 属性。

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


2
根据 MSDN 的说法,ReaderWriterLock 有一个有趣的实现:
  1. 一个线程可以持有读取器锁或写入器锁,但不能同时持有两者
  2. ReaderWriterLock 在读取器集合和一个写入器之间交替
  3. 当处于写入器队列中的线程正在等待活动读取器锁被释放时,请求新的读取器锁的线程会在读取器队列中累积。
根据调用堆栈,我们应该在 #66 处获得了写入锁,而最可能是在堆栈顶部的 PropertyChangedEventManager 上尝试获得读取器锁...
- Woodman
@Woodman 当同一个线程已经获取了写锁的情况下,它也可以获得读锁。我们可以通过简单的代码来验证这个问题。从堆栈跟踪中,前两个 get_ReadLock 是正常的,但在第三个时就停止了。 - Jeffrey Zhao
2
如果您当前持有写锁并想要获取读锁,则应调用“DowngradeFromWriterLock”;如果您拥有读锁并希望获取写锁并且当前持有读锁,则应调用“UpgradeToWriterLock”。 - Peter Ritchie
2
@JeffreyZhao 不,ReaderWriterLock 的文档非常明确:“一个线程可以持有读取器锁或写入器锁,但不能同时持有两者”,请参见 http://msdn.microsoft.com/zh-cn/library/system.threading.readerwriterlock(v=vs.110).aspx - Peter Ritchie
@JeffreyZhao 如果你拥有一个读锁并且需要写入,你必须升级为写锁,执行写操作,然后降级为读锁,如果不再需要读取,则解锁。 - Peter Ritchie
显示剩余7条评论

2

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

不要这样做。没有安全的方法可以从后台线程更新绑定到 UI 的对象。最好情况下它可能会工作一段时间,然后又因无法确定的原因而彻底崩溃。

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

如果您是 CPU 绑定,则需要将更新传回 UI 线程。我建议将其分批处理,而不是每个对象都进行线程传递,因为线程传递可能很昂贵。


1
我通常喜欢使用 Rx.Net 来处理对象批次的 CPU 绑定工作,然后将其调度到 UI 线程。它有很多有用的方法可以帮助我们完成这些工作。 - Aron
4
我不同意你的观点。理论上,进程中可能有多个 GUI/STA 线程,并且相同的模型可以绑定到不同 GUI 线程中的多个 GUI 组件。自 .NET 4.0 以来,从后台线程触发“PropertyChanged”事件是一种常见模式。 “PropertyChangedEventManager” 能够处理跨线程通知。将所有内容(需要批量)转换成 GUI 会带来复杂性,通常也会破坏简单的设计。 - Jeffrey Zhao

0

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

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

所有三次setter都是由于#54,#32和#10中的依赖属性更改而调用的。 您能否检查您的源代码并解释两件事:

  1. 第一次使用值为True的WindowViewModel.IsActive的更改如何导致将同一属性后续更改为False?(我可以假设False值进入WindowViewModel类的另一个实例,但是没有看到源代码,我不确定)
  2. 最重要的是,这个将False值分配给IsActive属性的操作如何反过来导致将同一属性的值分配为True?

这就是我认为您已经在IsActive属性上使用了TwoWay绑定,并建议在我的评论中将其替换为OneWay / OneWayToSource绑定的原因,因为这种更改将消除UI和ViewModel层之间可能的干扰。


实际上,这是从Window.IsActiveWindowView.IsActive的单向绑定。由于IsActive是只读属性,我们无法构建双向绑定。顺便说一下,我已经解决了这个问题并自己回答了这个问题。感谢您的帮助。 - Jeffrey Zhao

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接