Приложение WPF зависает от PropertyChangedEventManager в параллельных средах

Я работаю над сложным приложением WPF, которое зависает в производстве один раз в несколько дней. Есть поток, отличный от данных заполнения потока графического интерфейса пользователя для моделей, которые привязываются к сетке и запускают событие 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 ... )

То же самое происходит с потоком графического интерфейса:

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?

Пожалуйста, добавьте комментарии, если вам нужна дополнительная информация.


person Jeffrey Zhao    schedule 05.06.2014    source источник
comment
Что вы делаете в геттере IsActive? Он должен быть максимально простым (без блокировок, без операций ввода-вывода, без тяжелых вычислений). Кроме того, убедитесь, что событие ProppertyChanged запускается в потоке пользовательского интерфейса. Хотя, начиная с .NET Framework 4.0 (или более ранней?), Он автоматически отправляется в поток пользовательского интерфейса.   -  person Sergii Vashchyshchuk    schedule 10.06.2014
comment
@SergiiVashchyshchuk Геттер прост, как автоматическое свойство. Обеспечение срабатывания события PropertyChanged в потоке пользовательского интерфейса - это обходной путь, который я применил, но я хочу знать основную причину проблемы, поскольку теперь приемлемым шаблоном является уведомление об изменении свойства из фонового потока. Сейчас странно, почему поток графического интерфейса блокируется блокировкой чтения, когда он сам удерживает блокировку записи.   -  person Jeffrey Zhao    schedule 10.06.2014
comment
Он также использует этот ReadLock, когда подписывается на событие PropetyChanged. Он подписывается на событие PropetyChanged, когда вы привязываете свою модель к сетке. Как вы привязываете свою коллекцию моделей к сетке? Какой тип коллекции? Как вы его заполняете?   -  person Sergii Vashchyshchuk    schedule 11.06.2014
comment
@SergiiVashchyshchuk Он использует WriteLock для поддержки обработчиков событий. В привязке к сетке и обновлении моделей нет ничего особенного. Вы могли предположить, что это просто простой массив и простой набор свойств с запуском события PropertyChanged.   -  person Jeffrey Zhao    schedule 11.06.2014
comment
Надеюсь, вы профилировали его на предмет проблем с памятью, в противном случае я мог бы предложить пойти на профилирование памяти и посмотреть, есть ли что-то интересное, что нужно исправить. См. Этот пример в Википедии об утечке памяти, демонстрирующий аналогичную проблему с программным обеспечением лифтов. en.wikipedia.org/wiki/Memory_leak#An_example_of_memory_leak, просто чтобы упомянуть о старении программного обеспечения. здесь en.wikipedia.org/wiki/Software_aging   -  person pushpraj    schedule 11.06.2014
comment
@pushpraj Спасибо, что упомянули этот случай. Я посмотрю на это. Мы регулярно проводим профилирование памяти, особенно перед каждым выпуском, и эта проблема снова возникает в новом выпуске QA.   -  person Jeffrey Zhao    schedule 11.06.2014
comment
Итак, вы хотите сказать, что привязываете фиксированную коллекцию к элементам один раз, не изменяете ее, а только обновляете свойства элементов, заставляя их генерировать событие PropertyChanged? Еще вопросы, насколько велика ваша сетка (в пересчете на столбцы)? Сколько свойств у предметов у вас есть и как часто вы их изменяете?   -  person Sergii Vashchyshchuk    schedule 11.06.2014
comment
@SergiiVashchyshchuk ~ 20 сеток. Каждая сетка содержит от 1 до 20 тыс. Строк, а каждая строка - от 100 до 200 столбцов. Обновление происходит довольно часто, поскольку проблема обычно возникает, когда весь макет загружается с нуля.   -  person Jeffrey Zhao    schedule 11.06.2014
comment
Вы можете найти HwndSubclass здесь: referenceource.microsoft. com / # WindowsBase / src / Shared / MS / Win32 /, который, похоже, помогает с Win32. Также в комментариях говорится, что это не потокобезопасно. Возможно, просмотр поможет вам.   -  person Zache    schedule 11.06.2014
comment
@Zache Спасибо. Я тоже проверяю код оттуда. Параллелизм не является основной причиной. Я думаю, что решил проблему и отвечу на свой вопрос после дополнительной проверки.   -  person Jeffrey Zhao    schedule 11.06.2014
comment
@JeffreyZhao У вас есть проект, который можно воспроизвести? Я бы хотел посмотреть.   -  person 123 456 789 0    schedule 12.06.2014
comment
@lll Нет, мне не удалось написать простое приложение, в котором возникает проблема. Теперь я знаю причину, поэтому, возможно, я мог бы попробовать еще раз, но поскольку я все еще не могу на 100% объяснить причину, очень вероятно, что снова произойдет сбой. В любом случае, это интересный вопрос, заслуживающий изучения. Я размещу дополнительную информацию здесь.   -  person Jeffrey Zhao    schedule 13.06.2014
comment
Из стека вызовов похоже, что у вас есть двусторонняя привязка к свойству IsActive в вашей ViewModel, и это, похоже, вызывает коллизию. Можете ли вы изменить эту привязку на OneWayToSource или OneWay (в зависимости от логики этого свойства)?   -  person Woodman    schedule 13.06.2014
comment
@Woodman Почему проблема возникает из-за двусторонней привязки? В WindowViewModel.IsActive есть проверка, гарантирующая, что PropertyChanged событие срабатывает только тогда, когда новое значение не равно текущему значению. Я думаю, что свойство зависимости в элементе управления делает то же самое.   -  person Jeffrey Zhao    schedule 13.06.2014


Ответы (3)


Я наконец исправил проблему, покопавшись в исходном коде почти всех связанных компонентов. Благодаря фантастическому веб-сайту Reference Source, комментарии в исходном коде очень помогают по сравнению с результатом декомпилирования из ILSpy.

Что PropertyChangedEventManager?

PropertyChangedEventManager (здесь исходный код) - это компонент, который обрабатывает ProperyChanged обработчики событий и уведомления в параллельной среде. Другими словами, он потокобезопасный. Внутри он использует ReaderWriterLock для обеспечения безопасности потоков. Блокировка записи будет получена при изменении обработчиков событий, а блокировка чтения будет получена, когда появятся PropertyChanged уведомления о событиях.

PropertyChangedEventManager обычно используется элементами управления WPF. Когда мы присоединяем / отключаем модель представления к элементу управления, мы добавляем / удаляем PropertyChanged обработчики событий. Мне всегда было интересно, кто держит блокировку записи, которая блокирует блокировку чтения (get_ReadLock), но на самом деле это сам поток графического интерфейса.

Да, это звучит странно, но это просто внутри 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)
...

Кстати, мне всегда говорили, что мы должны запускать событие PropertyChanged в объекте, привязанном к пользовательскому интерфейсу, из фонового потока, но это не так, поскольку .NET 4. PropertyChangedEventManager предназначен для использования в параллельной среде. Эксклюзивная (писатель) блокировка будет использоваться только тогда, когда модель привязана к элементу управления GUI, и событие PropertyChanged может быть запущено из нескольких фоновых потоков одновременно. Нам не нужно вручную направлять все в поток графического интерфейса.

На самом деле это очень важный шаблон для обновления моделей в фоновом потоке, и иногда это единственный приемлемый подход. Пожалуйста, рассмотрите случай, когда у нас есть несколько потоков GUI / STA для повышения скорости отклика приложения. Мы могли бы привязать один и тот же экземпляр к элементам управления в другом потоке графического интерфейса. Когда модель изменяется, мы просто не можем перенаправить PropertyChanged уведомление ни в одну из них. Уведомление о перекрестной потоковой передаче неизбежно.

Что SubclassWndProc?

HwndSubclass.SubclassWndProc (здесь исходный код) - это точка входа управляемого кода. для обработки оконных сообщений. Он вызывается собственным кодом, поэтому мы всегда можем найти [IL Method without Metadata] и [Internal Frame, 'M-->U'] перед ним в трассировке стека.

Странно то, почему в трассировке стека может быть несколько SubclassWndProc вызовов? Разве оконные сообщения не следует обрабатывать одно за другим, отдельно? Чтобы ответить на вопрос, нам нужно проверить, что код методов неоднократно появляется в stacktrace:

...
 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. :

Отправляется как в активируемое, так и в деактивируемое окно. Если окна используют одну и ту же очередь ввода, сообщение отправляется синхронно, сначала оконной процедуре деактивируемого окна верхнего уровня, а затем оконной процедуре активируемого окна верхнего уровня. Если окна используют разные очереди ввода, сообщение отправляется асинхронно, поэтому окно активируется немедленно.

Поскольку сообщение отправляется синхронно в потоке графического интерфейса пользователя, оно будет обработано немедленно, без завершения текущего. По этой причине мы могли найти рекурсивные вызовы в трассировке стека.

Это также объясняет, почему WindowViewMode.IsActive устанавливается несколько раз:

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

Так как он будет «деактивирован» и «активирован» снова.

Основная причина и решение

В WindowViewModel у нас есть свойство IsActive, которое синхронизировано со свойством Window.IsActive. Обратите внимание, что это не двусторонняя привязка, поскольку свойство доступно только для чтения. Когда окно активировано, свойство WindowViewModel.IsActive будет установлено и инициировать событие PropertyChanged. Поскольку элемент управления WPF связан с моделью представления, внутренняя логика выполняется.

Я не совсем понимаю, в чем состоит логика (это [IL Method without Metadata]), но, к сожалению, это приводит к новому WM_ACTIVATE сообщению. Это происходит снова и снова и, наконец, останавливает поток графического интерфейса.

Убедившись, что мы не используем WindowViewModel.IsActive в привязке, я изменил его на метод IsActive(). Нам не нужно запускать событие PropertyChanged, поскольку оно больше не является свойством.

Я также оставил комментарий, в котором говорится, что если мы действительно требуем, чтобы IsActive было свойством, нам нужно убедиться, что событие PropertyChanged запускается внутри Dispatcher.BeginInvoke, даже если оно уже находится в потоке графического интерфейса. Нам нужно убедиться, что следующее WM_ACTIVATE сообщение создается асинхронно.

Одна вещь, которую я не могу объяснить

Но я до сих пор не могу объяснить, почему ReaderWriterLock будет блокироваться, когда мы получаем блокировку считывателя в третий или четвертый раз. Я действительно думаю, что у нас есть более глубокие рекурсивные PropertyChanged уведомления, поэтому блокировка считывателя будет использоваться больше раз, чем в текущем случае. Но каждый раз, когда мы сталкиваемся с этой проблемой, у нас всегда есть свойство IsActive в трассировке стека.

Есть ли какая-то особая защита в ReaderWriterLock или WPF или даже в ОС?

person Jeffrey Zhao    schedule 13.06.2014
comment
Согласно MSDN, у ReaderWriterLock есть интересная реализация: 1. Поток может удерживать блокировку чтения или блокировку записи, но не обе одновременно. 2. ReaderWriterLock чередуется между набором читателей и одним писателем. 3. Пока поток в писателе. очередь ожидает снятия активных блокировок считывателя, потоки, запрашивающие новые блокировки считывателя, накапливаются в очереди считывателя. И согласно стеку вызовов мы должны были получить блокировку записи в # 66 и, скорее всего, пытались получить ReadLock на том же PropertyChangedEventManager на вершине стека ... - person Woodman; 13.06.2014
comment
@Woodman Тот же поток мог получить блокировку чтения, когда он получил блокировку писателя. Мы можем проверить это простым кодом. Из трассировки стека первые два get_ReadLock в порядке, но остановились на третьем. - person Jeffrey Zhao; 13.06.2014
comment
Вы должны вызвать DowngradeFromWriterLock, если хотите получить блокировку Reader, когда у вас в настоящее время есть блокировка Writer; или позвоните UpgradeToWriterLock, если у вас есть блокировка чтения, и вы хотите получить блокировку Writer, а в настоящее время у вас есть блокировка Reader - person Peter Ritchie; 13.06.2014
comment
@PeterRitchie UpgradeToWriterLock предназначен для снятия блокировки считывателя, чтобы получить блокировку писателя. Он вернет LockCookie для перехода к DowngradeToWriterLock. Когда вы приобрели блокировку писателя, вы можете позвонить AcquireReaderLock, и у вас по-прежнему будет блокировка писателя. - person Jeffrey Zhao; 13.06.2014
comment
@JeffreyZhao Нет, документация для ReaderWriterLock очень ясна: поток может удерживать блокировку чтения или блокировку записи, но не обе одновременно, см. msdn.microsoft.com/en-us/library/ - person Peter Ritchie; 13.06.2014
comment
@JeffreyZhao Если у вас есть блокировка Reader и вам нужно писать, вы должны перейти на Writer, выполнить запись, затем перейти на Reader и затем разблокировать, если вам больше не нужно читать. - person Peter Ritchie; 13.06.2014
comment
@PeterRitchie Вы правы насчет перехода с блокировки чтения на блокировку записи, но в данном случае нам это не нужно. - person Jeffrey Zhao; 13.06.2014
comment
@PeterRitchie Когда поток удерживает блокировку записи, вы можете вызвать AcquireReaderLock, но у него все еще есть блокировка записи. Так написано в документации. Что касается метода DowngradeFromWriterLock, заметили ли вы, что он требует LockCookie получить от UpgradeToWriterLock? Напишите код для тестирования API. - person Jeffrey Zhao; 13.06.2014
comment
@JeffreyZhao В документации для AcquireReaderLock также указано, что если текущий поток уже имеет блокировку записи, блокировка чтения не устанавливается. Я не знаю, что еще сказать вам, в документации указано, что поток может удерживать блокировку чтения или блокировку записи, но не обе одновременно. Ни один тест не опровергает документацию. Если одновременное использование Reader и Writer не поддерживается, скорее всего, это состояние не определено, что означает, что иногда он не дает сбоя, а иногда и сбой. иногда это не подведет, это не значит, что это правильно. - person Peter Ritchie; 13.06.2014
comment
@PeterRitchie Вызов AcquireReaderLock, когда текущий поток удерживает блокировку записи, не дает вам блокировку чтения, это просто происходит ничего. Об этом говорит блокировка читателя не получена. - person Jeffrey Zhao; 13.06.2014
comment
Ну, дело не в том, что ничего не происходит - вы не получаете блокировку Reader, а ваш счетчик блокировок Writer увеличивается на единицу. (т.е. вы должны снять блокировку записи не впоследствии разблокировать блокировку чтения). Но я не рекомендую делать это таким образом, потому что вы должны знать, что у вас есть блокировка записи, и вызывать правильный метод Release, что действительно в некотором роде противоречит цели. - person Peter Ritchie; 13.06.2014

Есть поток, отличный от данных заполнения потока графического интерфейса пользователя для моделей, которые привязываются к сетке и запускают событие INotifyPropertyChanged.PropertyChanged.

Не делай этого. Нет безопасного способа обновить объект, привязанный к пользовательскому интерфейсу, из фонового потока. В лучшем случае это может работать какое-то время, а затем снова ужасно сломаться без видимой причины.

Если вы выполняете вызовы с привязкой к вводу-выводу, не используйте фоновый поток. Вместо этого используйте синтаксис async / await, чтобы избежать блокировки и обрабатывать все в потоке пользовательского интерфейса.

Если вы привязаны к процессору, вам необходимо маршалировать обновления обратно в поток пользовательского интерфейса. Я бы сделал это партиями, а не по каждому объекту, поскольку маршалинг потоков может быть дорогостоящим.

person Jonathan Allen    schedule 13.06.2014
comment
Обычно мне нравится использовать Rx.Net для выполнения работы, связанной с процессором, над пакетами объектов, а затем для маршалинга в поток пользовательского интерфейса. У него много методов, которые очень помогают. - person Aron; 13.06.2014
comment
Я с тобой не согласен. Теоретически в процессе может быть несколько потоков GUI / STA, и одни и те же модели могут быть привязаны к нескольким компонентам графического интерфейса в разных потоках графического интерфейса. Начиная с .NET 4.0, это нормальный шаблон для запуска события PropertyChanged из фонового потока. PropertyChangedEventManager хорошо обрабатывает перекрестные уведомления. Маршалинг всего (требуется пакетная обработка) в графический интерфейс усложняет и обычно нарушает простой дизайн. - person Jeffrey Zhao; 13.06.2014

Очень подозрительно выглядит то, что в одном и том же стеке вызовов вы меняете значение свойства IsActive 3 раза:

  1. в # 47 - WindowViewModel.set_IsActive (значение = True)
  2. в # 25 - WindowViewModel.set_IsActive (значение = False)
  3. в # 3 - WindowViewModel.set_IsActive (значение = True)

Все три раза сеттер был вызван в результате изменения свойства Dependency в # 54, # 32 и # 10 соответственно. Можете ли вы проверить свои источники и объяснить две вещи:

  1. Как может первое изменение WindowViewModel.IsActive со значением True привести к последующему изменению того же свойства на False? (я могу предположить, что значение False переходит к другому экземпляру класса WindowViewModel, но я не уверен, без видимых источников)
  2. и самое главное, как это присвоение значения False свойству IsActive, в свою очередь, приводит к присвоению значения True тому же свойству?

Это были причины, по которым я предположил, что вы использовали привязку TwoWay для свойства IsActive, и предложил заменить ее привязкой OneWay / OneWayToSource в моем комментарии, поскольку такое изменение устранит возможное взаимодействие между слоями UI и ViewModel здесь.

person Woodman    schedule 13.06.2014
comment
На самом деле это односторонняя привязка от Window.IsActive к WindowView.IsActive. Мы не можем построить двустороннюю привязку для IsActive, поскольку это свойство только для чтения. Кстати, я исправил проблему и сам отвечаю на вопрос. Спасибо за вашу помощь. - person Jeffrey Zhao; 13.06.2014