如何修复SignInManager.PasswordSignIn()上的“在此上下文上启动第二次操作”错误

时间:2019-08-13 14:04:43

标签: c# asp.net-mvc asp.net-identity owin

我有一个使用.net身份登录的MVC系统,但是,当两个单独的用户大致同时登录该系统时,最后单击该按钮的用户会看到一条错误消息;

  

发生一个或多个错误。在先前的异步操作完成之前,第二操作在此上下文上开始。使用“ await”来确保在此上下文上调用另一个方法之前,所有异步操作都已完成。不保证任何实例成员都是线程安全的。

此错误发生在SignInManager.PasswordSignInAsync()行上,但是我对自己做错了事感到困惑。

诚然,我对OWIN不太熟悉,但是如果有人对我可以采取什么行动来阻止这种情况有任何线索,将不胜感激。

为了掩盖一些事情,我已经尝试呼叫PasswordSignIn而不是PasswordSingInAsync,并且尝试等待呼叫,但是它是相同的故事-我相信这是因为这是两个完全分开的请求。

简而言之,我的代码设置如下;

LoginController.cs

public ActionResult Login(LoginModel model)
{
    _identityManagement.SignInManager.PasswordSignInAsync(model.Username, model.Password, model.PersistentLogin, false);

    //We do some stuff here but it fails before this point so code removed.
    return null;
}

IdentityManagement.cs

public class IdentityManagement
{
    public ApplicationSignInManager SignInManager
    {
        get
        {
            return HttpContext.Current.GetOwinContext().Get<ApplicationSignInManager>();
        }
    }
}

ApplicationSignInManager.cs

public class ApplicationSignInManager : SignInManager<SystemUser, string>
{
    public ApplicationSignInManager(ApplicationUserManager userManager, IAuthenticationManager authenticationManager) : base(userManager, authenticationManager)
    {
    }

    public static ApplicationSignInManager Create(IdentityFactoryOptions<ApplicationSignInManager> options, IOwinContext context)
    {
        return new ApplicationSignInManager(context.GetUserManager<ApplicationUserManager>(), context.Authentication);
    }
}

Startup.Auth.cs

public void ConfigureAuth(IAppBuilder app)
{
    DatabaseContext dbContext = DependencyResolver.Current.GetService<DatabaseContext>();

    app.CreatePerOwinContext<IUserStore<DatabaseContext>>(() => ApplicationUserStore.Create(dbContext));

    app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);

}

Global.asax.cs

var container = new SimpleInjector.Container();
container.Options.DefaultScopedLifestyle = new WebRequestLifestyle();
container.Register<DatabaseContext>(Lifestyle.Scoped);
container.Verify();

非常感谢, 汤姆

这里要求的是堆栈跟踪

System.AggregateException
HResult=0x80131500
Message=One or more errors occurred.
Source=mscorlib
StackTrace:
 at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions)
 at System.Threading.Tasks.Task`1.GetResultCore(Boolean waitCompletionNotification)
 at System.Threading.Tasks.Task`1.get_Result()
 at Controllers.LoginController.Login(LoginModel model) in C:\...\Controllers\LoginController.cs:line 205
 at Controllers.LoginController.Index(LoginModel model) in C:\...\Controllers\LoginController.cs:line 111
 at System.Web.Mvc.ActionMethodDispatcher.Execute(ControllerBase controller, Object[] parameters)
 at System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary`2 parameters)
 at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary`2 parameters)
 at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c.<BeginInvokeSynchronousActionMethod>b__9_0(IAsyncResult asyncResult, ActionInvocation innerInvokeState)
 at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult`2.CallEndDelegate(IAsyncResult asyncResult)
 at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResultBase`1.End()
 at System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethod(IAsyncResult asyncResult)
 at System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters.<>c__DisplayClass11_0.<InvokeActionMethodFilterAsynchronouslyRecursive>b__0()
 at System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters.<>c__DisplayClass11_2.<InvokeActionMethodFilterAsynchronouslyRecursive>b__2()

Inner Exception 1:
NotSupportedException: A second operation started on this context before a previous asynchronous operation completed. Use 'await' to ensure that any asynchronous operations have completed before calling another method on this context. Any instance members are not guaranteed to be thread safe.

3 个答案:

答案 0 :(得分:1)

问题在于,第二个用户(正在不同的线程中运行)正在尝试访问由第一个用户使用的DatabaseContext。您应该为每个线程创建一个新的DatabaseContext

答案 1 :(得分:1)

该错误说明了问题所在以及解决方法-异步操作中缺少await。唯一的此类操作在第一个代码段中:

public ActionResult Login(LoginModel model)
{
    _identityManagement.SignInManager.PasswordSignInAsync(model.Username, model.Password, model.PersistentLogin, false);

    //We do some stuff here but it fails before this point so code removed.
    return null;
}

根据约定,异步方法以Async结尾,然而,PasswordSignInAsync没有任何等待完成的事情,然后继续并返回。 PasswordSignInAsync确实是异步方法。

这意味着在此方法返回之前,用户尚未登录。所有人都知道登录可能失败。

在第一次尝试完成之前 可以轻松进行任何后续登录尝试。这也意味着永远不会设置登录Cookie,并且对该站点的任何后续调用都将尝试再次登录。

将方法更改为:

public async Task<ActionResult> Login(LoginModel model)
{
    var result=await _identityManagement.SignInManager.PasswordSignInAsync(model.Username, model.Password, model.PersistentLogin, false);
    if(result.Succeeded)
    {
        ....
    }
}

答案 2 :(得分:0)

谢谢你们,这使我走上了正确的轨道,现在我已经解决了问题。

Startup.auth.cs 中,我有以下代码

DatabaseContext dbContext = DependencyResolver.Current.GetService<DatabaseContext>();
app.CreatePerOwinContext<IUserStore<SystemUser>>(() => ApplicationUserStore.Create(dbContext));

我现在将其更改为以下选项,该选项适用于多个同时登录请求

app.CreatePerOwinContext<IUserStore<SystemUser>>(() => ApplicationUserStore.Create(new DatabaseContext()));

大概是我在这里声明dbContext在“ SystemUser”的每个实例中都使用相同的那个

再次感谢你们的帮助和想法。

汤姆