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 { }
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? –
@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ứ. –
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