能否让 Unity 始终不抛出 SynchronizationLockException?
Unity 依赖注入容器存在一个众所周知的问题,即 SynchronizedLifetimeManager 通常会导致 Monitor.Exit 方法抛出 SynchronizationLockException,然后该异常会被捕获并忽略。这对我来说是一个问题,因为我喜欢使用 Visual Studio 进行调试,设置为在任何引发的异常上中断,因此每次我的应用程序启动时,我都会无缘无故地多次中断此异常。
如何防止抛出此异常?
无论网络上其他地方提到此问题,建议通常都会涉及更改调试器设置以忽略它。这类似于去找医生说:“医生,医生,我举起手臂时会疼”,然后医生告诉他:“好吧,别再举起它了。”我正在寻找一种解决方案,可以首先阻止抛出异常。
异常发生在 SetValue 方法中,因为它假设首先调用 GetValue,然后调用 Monitor.Enter。但是,LifetimeStrategy 和 UnityDefaultBehaviorExtension 类都会定期调用 SetValue,而不调用 GetValue。
我不想更改源代码并维护我自己的 Unity 版本,因此我希望有一个解决方案,可以在其中向容器添加一些扩展、策略或策略的组合,以确保,如果生命周期管理器是一个 SynchronizedLifetimeManager,GetValue 总是在其他任何事情之前被调用。
The Unity dependency injection container has what seems to be a widely known issue where the SynchronizedLifetimeManager will often cause the Monitor.Exit method to throw a SynchronizationLockException which is then caught and ignored. This is a problem for me because I like to debug with Visual Studio set to break on any thrown exception, so every time my application starts up I'm breaking on this exception multiple times for no reason.
How can I prevent this exception from being thrown?
Wherever this issues is mentioned elsewhere on the web, the advice usually involves changing the debugger settings to ignore it. This is akin to going to the doctor and saying, "Doctor, Doctor, my arm hurts when I raise it," to be told, "Well, stop raising it." I'm looking for a solution that stops the exception being thrown in the first place.
The exception occurs in the SetValue method because it makes the assumption that GetValue will have been called first, where Monitor.Enter is called. However, the LifetimeStrategy and UnityDefaultBehaviorExtension classes both regularly call SetValue without calling GetValue.
I'd rather not have to change the source code and maintain my own version of Unity, so I'm hoping for a solution where I can add some combination of extensions, policies, or strategies to the container that will ensure that, if the lifetime manager is a SynchronizedLifetimeManager, GetValue is always called before anything else.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(8)
我确信代码可以通过很多方式调用 SynchronizedLifetimeManager 或像 ContainerControlledLifetimeManager 这样的后代,但是有两种情况特别给我带来了问题。
第一个是我自己的错误 - 我使用构造函数注入来提供对容器的引用,并且在该构造函数中,我还将该类的新实例添加到容器中以供将来使用。这种向后方法的效果是将生命周期管理器从 Transient 更改为 ContainerControlled,以便 Unity 调用 GetValue 的对象与其调用 SetValue 的对象不同。吸取的教训是在构建过程中不要执行任何可能更改对象生命周期管理器的操作。
第二种情况是每次调用RegisterInstance 时,UnityDefaultBehaviorExtension 都会调用SetValue,而不先调用GetValue。幸运的是,Unity 具有足够的可扩展性,只要有足够的热心,您就可以解决这个问题。
从这样的新行为扩展开始:
然后您需要一种方法来替换默认行为。 Unity 没有删除特定扩展的方法,因此您必须删除所有内容并再次将其他扩展放回原处:
注意到
UnityClearBuildPlanStrategies
了吗? RemoveAllExtensions 清除了容器的所有内部策略和策略列表(除了一个),因此我不得不使用另一个扩展来避免在恢复默认扩展时插入重复项:现在您可以安全地使用 RegisterInstance,而不必担心被逼到崩溃的边缘。疯狂。为了确定起见,这里有一些测试:
I'm sure there's a lot of ways code could call SynchronizedLifetimeManager, or a descendant like ContainerControlledLifetimeManager, but there were two scenarios in particular that were causing me problems.
The first was my own fault - I was using constructor injection to supply a reference to the container, and in that constructor I was also adding the new instance of the class to the container for future use. This backwards approach had the effect of changing the lifetime manager from Transient to ContainerControlled so that the object Unity called GetValue on was not the same object it called SetValue on. The lesson learned is don't do anything during build-up that could change an object's lifetime manager.
The second scenario was that every time RegisterInstance is called, UnityDefaultBehaviorExtension calls SetValue without calling GetValue first. Luckily, Unity is extensible enough that, with enough bloody-mindedness, you can work around the problem.
Start with a new behavior extension like this:
Then you need a way to replace the default behavior. Unity doesn't have a method to remove a specific extension, so you have to remove everything and put the other extensions back in again:
Notice that
UnityClearBuildPlanStrategies
? RemoveAllExtensions clears out all of the container's internal lists of policies and strategies except for one, so I had to use another extension to avoid inserting duplicates when I restored the default extensions:Now you can safely use RegisterInstance without fear of being driven to the brink of madness. Just to be sure, here's some tests:
已修复 在最新版本的 Unity (2.1.505.2) 中。通过 NuGet 获取它。
Fixed in the latest release of Unity (2.1.505.2). Get it via NuGet.
不幸的是,你的问题的答案是否定的。我与 Microsoft 模式与开发团队的开发团队跟进了此事。实践小组(直到最近我还是那里的开发负责人),我们将此作为 EntLib 5.0 需要考虑的错误。我们做了一些调查,得出的结论是,这是由我们的代码和调试器之间的一些意外交互引起的。我们确实考虑过修复,但事实证明这比现有代码更复杂。最后,这件事的优先级低于其他事情,并且没有达到 5 分的标准。
抱歉,我没有更好的答案给你。如果说这有什么安慰的话,我也觉得很烦人。
The answer to your question is unfortunately no. I followed up on this with the dev team here at the Microsoft patterns & practices group (I was the dev lead there until recently)and we had this as a bug to consider for EntLib 5.0. We did some investigation and came to the conclusion that this was caused by some unexpected interactions between our code and the debugger. We did consider a fix but this turned out to be more complex than the existing code. In the end this got prioritized below other things and didn't make the bar for 5.
Sorry I don't have a better answer for you. If it's any consolation I find it irritating too.
我使用这个简短的解决方案:
并像这样使用它:
问题是ContainerControlledLifetimeManager的基类期望SynchronizedSetValue通过base.GetValue执行monitor.Enter(),但是ContainerControlledLifetimeManager类无法做到这一点(显然是它的开发人员)没有启用“异常中断”?)。
问候,
科恩
I use this short solution:
and use it like this:
the problem is that the base class of ContainerControlledLifetimeManager expects a SynchronizedSetValue to do a monitor.Enter() via the base.GetValue, however the ContainerControlledLifetimeManager class fails to do this (apparently its developers didn't have 'break at exception' enabled?).
regards,
Koen
罗里的解决方案很棒 - 谢谢。解决了每天困扰我的问题!
我对 Rory 的解决方案做了一些小调整,以便它可以处理注册的任何扩展(在我的例子中,我有一个 WPF Prism/Composite 扩展)。
Rory's solution is great - thanks. Solved an issue that annoys me every day!
I made some minor tweaks to Rory's solution so that it handles whatever extensions are registered (in my case i had a WPF Prism/Composite extension)..
请注意 Zubin Appoo 的回答中的一个错误:他的代码中缺少 UnityClearBuildPlanStrategies。
正确的代码片段是:
Beware to one mistake in Zubin Appoo's answer: there is UnityClearBuildPlanStrategies missing in his code.
The right code snippet is:
Unity 2.1 - 2012年8月更新修复bug
解决线程安全问题:
http://unity.codeplex.com/discussions/328841
改善系统调试体验.Threading.SynchronizationLockException:
https://entlib.uservoice.com /forums/89245-general/suggestions/2377307-fix-the-system-threading-synchronizationlockexcep
在无法加载类型时通过更好的错误消息传递来改善调试体验:
http://unity.codeplex.com/workitem/9223
支持执行场景没有公共构造函数的类的现有实例上的 BuildUp():
http://unity.codeplex.com/workitem/9460
让更新体验变得如此简单对于用户而言,为了避免程序集绑定重定向的需要,我们选择仅增加程序集文件版本,而不是 .NET 程序集版本。
Unity 2.1 - August 2012 update fix the bug
Addressing a thread safety issue:
http://unity.codeplex.com/discussions/328841
Improving debugging experience on System.Threading.SynchronizationLockException:
https://entlib.uservoice.com/forums/89245-general/suggestions/2377307-fix-the-system-threading-synchronizationlockexcep
Improving debugging experience through better error messaging when a type cannot be loaded:
http://unity.codeplex.com/workitem/9223
Supporting a scenario of performing a BuildUp() on an existing instance of a class that doesn’t have a public constructor:
http://unity.codeplex.com/workitem/9460
To make the update experience as simple as possible for users and to avoid the need for assembly binding redirects, we chose to only increment the assembly file version, not the .NET assembly version.
这可能对您有帮助:
瞧。
This might help you:
Voila.