2010-05-20 22 views
64

Thùng chứa phụ thuộc Unity có những gì có vẻ là một vấn đề được biết đến rộng rãi khi SynchronizedLifetimeManager thường sẽ gây ra phương thức Monitor.Exit để đưa ra một SynchronizationLockException mà sau đó bị bắt và bỏ qua. Đây là một vấn đề đối với tôi bởi vì tôi muốn gỡ lỗi với Visual Studio thiết lập để phá vỡ bất kỳ ngoại lệ ném, vì vậy mỗi khi ứng dụng của tôi bắt đầu lên tôi đang vi phạm ngoại lệ này nhiều lần không có lý do.Unity có thể được thực hiện để không đồng bộ hóa bất cứ lúc nào?

Làm cách nào để ngăn chặn ngoại lệ này bị ném?

Bất cứ nơi nào vấn đề này được đề cập ở đâu đó trên web, lời khuyên thường liên quan đến việc thay đổi cài đặt trình gỡ lỗi để bỏ qua nó. Điều này cũng giống như đi đến bác sĩ và nói, "Bác sĩ, Bác sĩ, cánh tay của tôi đau khi tôi giơ nó lên," được nói, "Chà, đừng ngừng nuôi nó nữa." Tôi đang tìm một giải pháp ngăn chặn ngoại lệ bị ném ngay từ đầu.

Trường hợp ngoại lệ xảy ra trong phương thức SetValue vì nó giả định rằng GetValue sẽ được gọi trước tiên, trong đó Monitor.Enter được gọi. Tuy nhiên, các lớp LifetimeStrategy và UnityDefaultBehaviorExtension cả hai đều thường gọi SetValue mà không cần gọi GetValue.

Tôi không muốn thay đổi mã nguồn và duy trì phiên bản Unity của riêng mình, vì vậy tôi hy vọng giải pháp mà tôi có thể thêm một số kết hợp các tiện ích, chính sách hoặc chiến lược vào vùng chứa rằng, nếu người quản lý lâu dài là một SynchronizedLifetimeManager, GetValue luôn được gọi trước bất cứ điều gì khác.

Trả lời

38

Tôi chắc chắn có rất nhiều cách mã có thể gọi SynchronizedLifetimeManager hoặc con cháu như ContainerControlledLifetimeManager, nhưng có hai trường hợp đặc biệt gây ra sự cố cho tôi.

Đầu tiên là lỗi của riêng tôi - tôi đã sử dụng hàm tạo để cung cấp tham chiếu đến vùng chứa và trong hàm tạo đó, tôi cũng thêm cá thể mới của lớp vào vùng chứa để sử dụng trong tương lai. Cách tiếp cận ngược này có tác dụng thay đổi trình quản lý suốt đời từ Transient thành ContainerControlled sao cho đối tượng Unity được gọi là GetValue không phải là cùng một đối tượng được gọi là SetValue. Bài học kinh nghiệm là không làm bất cứ điều gì trong quá trình xây dựng có thể thay đổi người quản lý suốt đời của đối tượng.

Kịch bản thứ hai là mỗi khi RegisterInstance được gọi, UnityDefaultBehaviorExtension gọi SetValue mà không cần gọi trước GetValue. May mắn thay, Unity có thể mở rộng đủ, với đủ tâm trí đẫm máu, bạn có thể giải quyết vấn đề.

Bắt đầu với một phần mở rộng hành vi mới như thế này:

/// <summary> 
/// Replaces <see cref="UnityDefaultBehaviorExtension"/> to eliminate 
/// <see cref="SynchronizationLockException"/> exceptions that would otherwise occur 
/// when using <c>RegisterInstance</c>. 
/// </summary> 
public class UnitySafeBehaviorExtension : UnityDefaultBehaviorExtension 
{ 
    /// <summary> 
    /// Adds this extension's behavior to the container. 
    /// </summary> 
    protected override void Initialize() 
    { 
     Context.RegisteringInstance += PreRegisteringInstance; 

     base.Initialize(); 
    } 

    /// <summary> 
    /// Handles the <see cref="ExtensionContext.RegisteringInstance"/> event by 
    /// ensuring that, if the lifetime manager is a 
    /// <see cref="SynchronizedLifetimeManager"/> that its 
    /// <see cref="SynchronizedLifetimeManager.GetValue"/> method has been called. 
    /// </summary> 
    /// <param name="sender">The object responsible for raising the event.</param> 
    /// <param name="e">A <see cref="RegisterInstanceEventArgs"/> containing the 
    /// event's data.</param> 
    private void PreRegisteringInstance(object sender, RegisterInstanceEventArgs e) 
    { 
     if (e.LifetimeManager is SynchronizedLifetimeManager) 
     { 
      e.LifetimeManager.GetValue(); 
     } 
    } 
} 

Sau đó, bạn cần một cách để thay thế các hành vi mặc định. Unity không có một phương pháp để loại bỏ một phần mở rộng cụ thể, vì vậy bạn phải loại bỏ tất cả mọi thứ và đưa các phần mở rộng khác lại một lần nữa:

public static IUnityContainer InstallCoreExtensions(this IUnityContainer container) 
{ 
    container.RemoveAllExtensions(); 
    container.AddExtension(new UnityClearBuildPlanStrategies()); 
    container.AddExtension(new UnitySafeBehaviorExtension()); 

#pragma warning disable 612,618 // Marked as obsolete, but Unity still uses it internally. 
    container.AddExtension(new InjectedMembers()); 
#pragma warning restore 612,618 

    container.AddExtension(new UnityDefaultStrategiesExtension()); 

    return container; 
} 

ý rằng UnityClearBuildPlanStrategies? RemoveAllExtensions gạt bỏ tất cả các danh sách nội bộ của container của các chính sách và chiến lược ngoại trừ một, vì vậy tôi đã phải sử dụng phần mở rộng khác để tránh chèn các bản sao khi tôi phục hồi các phần mở rộng mặc định:

/// <summary> 
/// Implements a <see cref="UnityContainerExtension"/> that clears the list of 
/// build plan strategies held by the container. 
/// </summary> 
public class UnityClearBuildPlanStrategies : UnityContainerExtension 
{ 
    protected override void Initialize() 
    { 
     Context.BuildPlanStrategies.Clear(); 
    } 
} 

Bây giờ bạn có thể an toàn sử dụng RegisterInstance mà không sợ bị đẩy tới bờ vực điên rồ. Để chắc chắn, dưới đây là một số thử nghiệm:

[TestClass] 
public class UnitySafeBehaviorExtensionTests : ITest 
{ 
    private IUnityContainer Container; 
    private List<Exception> FirstChanceExceptions; 

    [TestInitialize] 
    public void TestInitialize() 
    { 
     Container = new UnityContainer(); 
     FirstChanceExceptions = new List<Exception>(); 
     AppDomain.CurrentDomain.FirstChanceException += FirstChanceExceptionRaised; 
    } 

    [TestCleanup] 
    public void TestCleanup() 
    { 
     AppDomain.CurrentDomain.FirstChanceException -= FirstChanceExceptionRaised; 
    } 

    private void FirstChanceExceptionRaised(object sender, FirstChanceExceptionEventArgs e) 
    { 
     FirstChanceExceptions.Add(e.Exception); 
    } 

    /// <summary> 
    /// Tests that the default behavior of <c>UnityContainer</c> leads to a <c>SynchronizationLockException</c> 
    /// being throw on <c>RegisterInstance</c>. 
    /// </summary> 
    [TestMethod] 
    public void UnityDefaultBehaviorRaisesExceptionOnRegisterInstance() 
    { 
     Container.RegisterInstance<ITest>(this); 

     Assert.AreEqual(1, FirstChanceExceptions.Count); 
     Assert.IsInstanceOfType(FirstChanceExceptions[0], typeof(SynchronizationLockException)); 
    } 

    /// <summary> 
    /// Tests that <c>UnitySafeBehaviorExtension</c> protects against <c>SynchronizationLockException</c>s being 
    /// thrown during calls to <c>RegisterInstance</c>. 
    /// </summary> 
    [TestMethod] 
    public void SafeBehaviorPreventsExceptionOnRegisterInstance() 
    { 
     Container.RemoveAllExtensions(); 
     Container.AddExtension(new UnitySafeBehaviorExtension()); 
     Container.AddExtension(new InjectedMembers()); 
     Container.AddExtension(new UnityDefaultStrategiesExtension()); 

     Container.RegisterInstance<ITest>(this); 

     Assert.AreEqual(0, FirstChanceExceptions.Count); 
    } 
} 

public interface ITest { } 
+0

Chúng tôi đang sử dụng Enterprise Library Logger nhưng không phải Unity và chúng tôi có cùng một vấn đề kể từ khi logger sử dụng Unity trong nội bộ. Tôi có thể áp dụng bản sửa lỗi đó mà không có Unity không? –

+0

@DmitryGusarov Tôi đã không thử nó, nhưng nếu bạn có thể có được một tham chiếu đến dụ 'UnityContainer' EntLib sử dụng nội bộ, sau đó bạn sẽ có thể sử dụng phương pháp' InstallCoreExtensions' trên nó. Bạn nên kiểm tra xem EntLib không thêm bất kỳ phần mở rộng nào của chính nó hay không - nếu có, bạn sẽ cần phải thêm các phần mở rộng đó sau khi 'InstallCoreExtensions' loại bỏ mọi thứ. –

+4

KHÔNG sử dụng giải pháp này với phiên bản Unity mới nhất, nó tạo ra các vòng vô hạn. – Softlion

-8

Điều này có thể giúp bạn:

  • Đến Debug -> Exceptions ...
  • Tìm các ngoại lệ mà thực sự khó chịu bạn như SynchronizationLockException

Voila.

+3

Đó là ý của tôi, "lời khuyên thường liên quan đến việc thay đổi cài đặt trình gỡ lỗi để bỏ qua nó". Tôi không muốn để đè bẹp bất kỳ SynchronizationLockExceptions rằng lỗi trong mã của riêng tôi có thể gây ra. –

10

Rất tiếc, câu trả lời cho câu hỏi của bạn là không. Tôi theo dõi điều này với nhóm dev ở đây tại các mẫu thực hành của Microsoft & nhóm thực hành (tôi là người dẫn đầu ở đó cho đến gần đây) và chúng tôi đã có lỗi này để xem xét cho EntLib 5.0. Chúng tôi đã thực hiện một số điều tra và đi đến kết luận rằng điều này là do một số tương tác không mong muốn giữa mã của chúng tôi và trình gỡ rối. Chúng tôi đã xem xét khắc phục nhưng điều này hóa ra phức tạp hơn mã hiện tại. Cuối cùng điều này đã được ưu tiên bên dưới những thứ khác và không làm cho thanh cho 5.

Xin lỗi tôi không có câu trả lời tốt hơn cho bạn. Nếu đó là bất kỳ sự an ủi nào, tôi cũng thấy nó khó chịu.

+1

Cảm ơn câu trả lời từ miệng của con ngựa! Hạnh phúc, Unity có thể mở rộng đủ để tôi có thể giải quyết vấn đề - xem [answer] của tôi (http://stackoverflow.com/questions/2873767/can-unity-be-made-to-not-throw-synchronizationlockexception -all-the-time/3153585 # 3153585). –

+0

Ade hoàn toàn tóm tắt quyết định mà nhóm EntLib đã trải qua tại thời điểm phát triển EntLib5.0. Nếu điều này tiếp tục là một yếu tố gây khó chịu lớn đối với nhiều người, vui lòng đề nghị nó trên trang web của chúng tôi và bỏ phiếu cho nó. Chúng ta sẽ xem xét nó khi lập kế hoạch vNext of Unity và EntLib. –

4

Giải pháp của Rory là tuyệt vời - cảm ơn. Giải quyết một vấn đề làm phiền tôi mỗi ngày! tôi thực hiện một số tinh chỉnh nhỏ để giải pháp Rory sao cho nó xử lý bất cứ phần mở rộng đã được đăng ký (trong trường hợp của tôi, tôi đã có một/extension Composite WPF Prism) ..

public static void ReplaceBehaviourExtensionsWithSafeExtension(IUnityContainer container) 
    { 
     var extensionsField = container.GetType().GetField("extensions", BindingFlags.Instance | BindingFlags.NonPublic); 
     var extensionsList = (List<UnityContainerExtension>)extensionsField.GetValue(container); 
     var existingExtensions = extensionsList.ToArray(); 
     container.RemoveAllExtensions(); 
     container.AddExtension(new UnitySafeBehaviorExtension()); 
     foreach (var extension in existingExtensions) 
     { 
      if (!(extension is UnityDefaultBehaviorExtension)) 
      { 
       container.AddExtension(extension); 
      } 
     } 
    } 
+0

Hãy nhớ xóa các chiến lược kế hoạch xây dựng - 'container.AddExtension (new UnityClearBuildPlanStrategies());' –

1

Cẩn thận với một sai lầm trong câu trả lời Zubin Appoo của: có UnityClearBuildPlanChiến lược bị thiếu trong mã của anh ấy.

Đoạn mã đúng là:

FieldInfo extensionsField = container.GetType().GetField("extensions", BindingFlags.Instance | BindingFlags.NonPublic); 
List<UnityContainerExtension> extensionsList = (List<UnityContainerExtension>)extensionsField.GetValue(container); 
UnityContainerExtension[] existingExtensions = extensionsList.ToArray(); 
container.RemoveAllExtensions(); 
container.AddExtension(new UnityClearBuildPlanStrategiesExtension()); 
container.AddExtension(new UnitySafeBehaviorExtension()); 

foreach (UnityContainerExtension extension in existingExtensions) 
{ 
    if (!(extension is UnityDefaultBehaviorExtension)) 
    { 
     container.AddExtension(extension); 
    } 
} 
+1

Đoạn mã này giống với câu trả lời của Zubin và không bao gồm lệnh gọi đến UnityClearBuildPlanStrategies() –

+0

Bạn đúng, cố định –

7

tôi sử dụng giải pháp ngắn này:

/// <summary> 
/// KVV 20110502 
/// Fix for bug in Unity throwing a synchronizedlockexception at each register 
/// </summary> 
class LifeTimeManager : ContainerControlledLifetimeManager 
{ 
    protected override void SynchronizedSetValue(object newValue) 
    { 
     base.SynchronizedGetValue(); 
     base.SynchronizedSetValue(newValue); 
    } 
} 

và sử dụng nó như thế này:

private UnityContainer _container; 
... 
_container.RegisterInstance(instance, new LifeTimeManager()); 

vấn đề là các lớp cơ sở của ContainerControlledLifetimeManager mong đợi một SynchronizedSetValue để thực hiện một monitor.Enter() thông qua base.GetValue, ho wever lớp ContainerControlledLifetimeManager không làm điều này (dường như các nhà phát triển của nó không có 'break at exception' được kích hoạt?).

regards, Koen

+0

Đây là một giải pháp tốt cho Silverlight! – Kit

+0

Làm thế nào tôi có thể giới thiệu bản sửa lỗi này vào ứng dụng? Tôi không hiểu ... Tôi có nên đăng ký LifeTimeManager ở đâu đó không? Chúng tôi không sử dụng Unity ... Chỉ cần Logger này ... –

12

Fixed trong bản phát hành mới nhất của Unity (2.1.505.2). Nhận nó qua NuGet.

0

Unity 2.1 - August 2012 update fix the bug

  1. Phát biểu một vấn đề an toàn thread: http://unity.codeplex.com/discussions/328841

  2. Cải thiện kinh nghiệm gỡ lỗi trên System.Threading.SynchronizationLockException: https://entlib.uservoice.com/forums/89245-general/suggestions/2377307-fix-the-system-threading-synchronizationlockexcep

  3. Cải thiện kinh nghiệm gỡ lỗi thông qua tin nhắn lỗi tốt hơn khi một loại không thể được nạp: http://unity.codeplex.com/workitem/9223

  4. Hỗ trợ một kịch bản thực hiện một sự tích tụ() trên một thể hiện hiện của một lớp mà không làm có một constructor công cộng: http://unity.codeplex.com/workitem/9460

để làm cho kinh nghiệm bản cập nhật như đơn giản càng tốt cho người sử dụng và để tránh các nee d cho chuyển hướng ràng buộc lắp ráp, chúng tôi đã chọn chỉ tăng phiên bản tệp lắp ráp, chứ không phải phiên bản lắp ráp .NET.

Các vấn đề liên quan