我有一个Connections需要异步初始化的类型.这种类型的实例被其他几种类型(例如Storage)消耗,每种类型也需要异步初始化(静态,不是每个实例,并且这些初始化也依赖于Connections).最后,我的逻辑类型(例如Logic)使用这些存储实例.目前使用Simple Injector.
我尝试了几种不同的解决方案,但总有一种反模式存在.
我目前使用的解决方案有Temporal Coupling反模式:
public sealed class Connections
{
Task InitializeAsync();
}
public sealed class Storage : IStorage
{
public Storage(Connections connections);
public static Task InitializeAsync(Connections connections);
}
public sealed class Logic
{
public Logic(IStorage storage);
}
public static class GlobalConfig
{
public static async Task EnsureInitialized()
{
var connections = Container.GetInstance<Connections>();
await connections.InitializeAsync();
await Storage.InitializeAsync(connections);
}
}
Run Code Online (Sandbox Code Playgroud)
我已经将Temporal Coupling封装成一种方法,所以它并没有那么糟糕.但是,它仍然是一个反模式,而不是像我想的那样可维护.
常见的解决方案是抽象工厂模式.但是,在这种情况下,我们正在处理异步初始化.因此,我可以通过强制初始化同步运行来使用抽象工厂,但这会采用同步异步反模式.我真的不喜欢异步同步方法,因为我有几个存储空间,在我当前的代码中,它们都是同时初始化的; 由于这是一个云应用程序,将其更改为串行同步会增加启动时间,并且由于资源消耗,并行同步也不理想.
我也可以使用Abstract Factory和异步工厂方法.但是,这种方法存在一个主要问题.正如马克·西曼(Mark …
dependency-injection initialization abstract-factory async-await simple-injector
更新:@usr指出我错误地认为Lazy<T>默认的线程安全模式是LazyThreadSafetyMode.PublicationOnly......
我想懒洋洋地通过async工厂方法计算一个值(即它返回Task<T>)并在成功时缓存它.在例外情况下,我想让我可以使用它.我不然而,要堕入了异常缓存的行为是Lazy<T>在其默认模式(LazyThreadSafetyMode.ExecutionAndPublication)
异常缓存:使用工厂方法时,会缓存异常.也就是说,如果工厂方法在线程第一次尝试访问Lazy对象的Value属性时抛出异常,则每次后续尝试都会抛出相同的异常.这确保了对Value属性的每次调用都会产生相同的结果,并避免在不同的线程获得不同结果时可能出现的细微错误.懒惰代表一个实际的T,否则它将在某些早期点(通常在启动期间)初始化.在那个早期点失败通常是致命的.如果存在可恢复故障的可能性,我们建议您将重试逻辑构建到初始化例程(在本例中为工厂方法),就像您不使用延迟初始化一样.
Stephen Toub的AsyncLazy课程和写作似乎恰到好处:
public class AsyncLazy<T> : Lazy<Task<T>>
{
public AsyncLazy(Func<Task<T>> taskFactory) :
base(() => Task.Factory.StartNew(() => taskFactory()).Unwrap())
{ }
public TaskAwaiter<T> GetAwaiter() { return Value.GetAwaiter(); }
}
Run Code Online (Sandbox Code Playgroud)
然而,这实际上与默认行为相同Lazy<T>- 如果出现问题,则不会重试.
我正在寻找一个Task<T>兼容的等价物Lazy<T>(Func<T>, LazyThreadSafetyMode.PublicationOnly),即它应该按照指定的行为: -
锁定的替代方法在某些情况下,您可能希望避免Lazy对象的默认锁定行为的开销.在极少数情况下,可能存在死锁的可能性.在这种情况下,您可以使用Lazy(LazyThreadSafetyMode)或Lazy(Func,LazyThreadSafetyMode)构造函数,并指定LazyThreadSafetyMode.PublicationOnly.如果线程同时调用Value属性,这使Lazy对象能够在多个线程中的每个线程上创建一个延迟初始化对象的副本.Lazy对象确保所有线程使用延迟初始化对象的相同实例并丢弃未使用的实例.因此,降低锁定开销的成本是您的程序有时可能会创建并丢弃昂贵对象的额外副本.在大多数情况下,这不太可能.Lazy(LazyThreadSafetyMode)和Lazy(Func,LazyThreadSafetyMode)构造函数的示例演示了此行为.
重要
指定PublicationOnly时,即使指定了工厂方法,也不会缓存异常.
是否有任何FCL Nito.AsyncEx或类似的结构可能适合这里?如果没有这个,任何人都可以看到一种优雅的方式来控制"尝试进行中"位(我很好,每个调用者以与Lazy<T>(... 相同的方式进行自己的尝试(LazyThreadSafetyMode.PublicationOnly))并且仍然具有该缓存管理封装整齐?