ConfigureAuth 中的 ArgumentNull - 竞争条件?

ArgumentNull in ConfigureAuth - race condition?

使用 ASP.NET 与 Ninject 的身份,我的 Startup.Auth.cs 中有这个。它主要是样板文件:

private static StandardKernel _kernel;

private static StandardKernel CreateKernel()
{
    if (_kernel == null)
    {
        System.Diagnostics.Debug.WriteLine("Creating Kernel");
        _kernel = new StandardKernel();
        _kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();

        _kernel.Load(new Services.ServiceModule());
    }
    return _kernel;
}

// For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
public void ConfigureAuth(IAppBuilder app)
{
    // Configure the db context, user manager and signin manager to use a single instance per request
    //app.CreatePerOwinContext(CreateKernel);
    app.UseNinjectMiddleware(CreateKernel);
    app.CreatePerOwinContext<MyApp.Dal.IDataAccessService>(() => 
    {
        // this occassionally throws a ArgumentNullException 
        // and _kernel is null in the debugger...?
        return _kernel.Get<MyApp.Dal.IDataAccessService>();
    });
    //... lots more config stuff
}

问题是这一行:

return _kernel.Get<MyApp.Dal.IDataAccessService>();

偶尔会抛出一个 ArgumentNullException 因为 _kernel 为空。这似乎发生在我第一次启动我的网络应用程序时,尤其是当我在浏览器中关闭该页面之前完成加载并打开另一个页面时。我相信这是一个竞争条件,但我可以确切地弄清楚这里的事件顺序是什么以及如何正确同步它。

我在有问题的行周围添加了一个 try catch,这样我就可以看到它在哪个线程上执行(并将相同的信息添加到 Creating Kernel 调试行,我看到了这个:

Creating Kernel on 7

Exception thrown: 'System.ArgumentNullException' in Ninject.dll

Exception caught on 8: Cannot be null

很明显,问题是 CreateKernel 与尝试 Get MyApp.Dal.IDataAccessService 的实例在不同的线程上。同步它的正确方法是什么?

堆栈跟踪:

   at System.ThrowHelper.ThrowArgumentException(ExceptionResource resource)
   at System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)
   at System.Collections.Generic.Dictionary`2.Add(TKey key, TValue value)
   at Ninject.KernelBase.Load(IEnumerable`1 m)
   at Ninject.ModuleLoadExtensions.Load(IKernel kernel, INinjectModule[] modules)
   at MyApp.Startup.CreateKernel() in C:\Users\matt.burland\Documents\Visual Studio 2015\Projects\MyApp\Trunk\MyApp\MyApp\App_Start\Startup.Auth.cs:line 33
   at MyApp.Startup.<>c.<ConfigureAuth>b__3_0() in C:\Users\matt.burland\Documents\Visual Studio 2015\Projects\MyApp\Trunk\MyApp\MyApp\App_Start\Startup.Auth.cs:line 52
   at Owin.AppBuilderExtensions.<>c__DisplayClass1`1.<CreatePerOwinContext>b__0(IdentityFactoryOptions`1 options, IOwinContext context)
   at Microsoft.AspNet.Identity.Owin.IdentityFactoryProvider`1.Create(IdentityFactoryOptions`1 options, IOwinContext context)
   at Microsoft.AspNet.Identity.Owin.IdentityFactoryMiddleware`2.<Invoke>d__0.MoveNext()

尝试使用锁来缓解竞争条件。此外,当从表达式调用时,内核在被调用时仍有可能为空,因此请使用该方法以防万一。

private static StandardKernel _kernel;
private static object syncLock = new object();

private static StandardKernel GetKernel() {
    if (_kernel == null) {
        lock(syncLock) {
            if (_kernel == null) {
                System.Diagnostics.Debug.WriteLine("Creating Kernel");
                _kernel = new StandardKernel();
                _kernel.Bind<IHttpModule().To<HttpApplicationInitializationHttpModule>();
                _kernel.Load(new Services.ServiceModule());
            }
        }
    }
    return _kernel;
}


public void ConfigureAuth(IAppBuilder app) {
    // Configure the db context, user manager and signin manager to use a single instance per request
    app.UseNinjectMiddleware(GetKernel);
    app.CreatePerOwinContext<MyApp.Dal.IDataAccessService>(() => 
    {
       return GetKernel().Get<MyApp.Dal.IDataAccessService>();
    });
    //... lots more config stuff
}