我们有一个WPF应用程序(.NET 4.0,由于Windows Server 2003和XP兼容性而无法更改),该应用程序使用BCL支持异步/等待。
对于DI和方面,我们使用Castle IoC,而要访问Oracle数据库,则使用NHibernate。我们的问题如下:

我已经实现了一个UoW模式,该模式有时必须接收一个ISessionFactory。由于我们使用async / await,因此用户调用的第一个业务操作将首次加载ISessionFactory单例,这可能需要花费几秒钟的时间(由于映射(即使我将该信息存储在文件中)),并且async / await将在UI上执行,从而阻止应用程序。我要实现的目标是将ISessionFactory加载到单独的线程上,试图防止UI阻塞。由于这种首次加载可能在任何给定的时间在任何屏幕上发生,因此我认为IoC容器应该是放置此逻辑的理想场所,但是我无法防止UI阻塞。

这是正常的注册:

    Component.For<ISessionFactory>().UsingFactoryMethod(
        k =>
            Fluently.Configure()
                .Database(
                    () => OracleClientConfiguration.Oracle10.ConnectionString(
                        c => c.Is(k.Resolve<ISifarmaConnectionString>().Value))
#if DEBUG
                    .ShowSql().FormatSql()
#endif
                )
                .Mappings(m => m.FluentMappings.AddFromAssemblyOf<ISifarmaUnitOfWork>())
#if DEBUG
                .ExposeConfiguration(c => c.SetInterceptor(new SqlStatementInterceptor()))
#endif
                .BuildSessionFactory()).LifestyleSingleton(),


这是尝试过的方法(因为我返回的是具体类型,而不是我不能在这里等待的Task<ISessionFactory>,这显然用Task.WaitAll(task)阻止了UI):

    Component.For<ISessionFactory>().UsingFactoryMethod(
        k =>
        {
            var task =
                Task.Factory.StartNew(
                    () =>
                        Fluently.Configure()
                            .Database(
                                () => OracleClientConfiguration.Oracle10.ConnectionString(
                                    c => c.Is(k.Resolve<ISifarmaConnectionString>().Value))
#if DEBUG
                                .ShowSql().FormatSql()
#endif
                            )
                            .Mappings(m => m.FluentMappings.AddFromAssemblyOf<ISifarmaUnitOfWork>())
#if DEBUG
                            .ExposeConfiguration(c => c.SetInterceptor(new SqlStatementInterceptor()))
#endif
                            .BuildSessionFactory());
            Task.WaitAll(task);
            return task.Result;
        }).LifestyleSingleton(),


有什么方法可以在不阻止UI的情况下执行此操作?
当应用程序启动时,我还尝试创建一个创建container.Resolve<ISessionFactory>()的任务,但是如果用户在登录屏幕上足够快,则该对象尚未加载。

最佳答案

您应该做的是为ISessionFactory创建一个依赖于Lazy<ISessionFactory>的代理类。该代理可以注入需要ISessionFactory的任何人,并且您可以在初始化期间在后台线程中触发自己创建ISessionFactory

public class LazySessionFactoryProxy : ISessionFactory
{
    private readonly Lazy<ISessionFactory> factory;

    public LazySessionFactoryProxy(Lazy<ISessionFactory> factory) {
        this.factory = factory;
    }

    public ISession OpenSession() {
        return this.factory.Value.OpenSession();
    }
}


可以注册如下:

var lazy = new Lazy<ISessionFactory>(() => ...);

container.Register(Component.For<ISessionFactory>()
    .Instance(new LazySessionFactoryProxy(lazy)));

Task.Factory.StartNew(() => lazy.Value);


通过在合成根目录内创建代理类,您可以使应用程序的其余部分忽略会话工厂中的性能瓶颈。向每个使用者注入Lazy<ISessionFactory>Task<ISessionFactory>意味着您需要在整个应用程序中进行彻底更改(违反OCP),并且意味着您泄漏了实现细节(事实是特定会话工厂的创建成本很高)超出抽象(违反DIP)。

您甚至可以创建一个LazySessionFactoryProxy,当某些代码调用OpenSession时将显示一个等待屏幕,并在工厂初始化时自动将其关闭。有趣的是,这一切都是可能的,而无需在应用程序中更改一行代码。只需更改合成根的布线即可。

请注意,DI容器不支持“异步解析”的问题,但是使用异步解析根本没有用。之所以构建您的object graphs should be really fast是因为应该以我展示的方式隔离injection constructors should be simple和较慢的部分,而不是用它污染应用程序。

09-15 19:57