如何修复 SignInManager.PasswordSignIn() 上的 'A second operation started on this context' 错误

How to fix 'A second operation started on this context' error on SignInManager.PasswordSignIn()

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

One or more errors occurred. 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.

此错误发生在 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.

问题是第二个用户(运行 在不同的线程中)在第一个用户使用 DatabaseContext 时试图访问它。您应该为每个线程创建一个新的 DatabaseContext

该错误解释了问题所在以及如何修复它 - 异步操作中缺少 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确实是异步方法

这意味着用户在使用此方法时尚未登录 returns。对于所有人都知道登录可能已经失败。

任何后续登录尝试都可以很容易地发生在第一次尝试完成之前。这也意味着永远不会设置登录 cookie,并且对该站点的任何后续调用都将尝试再次登录。

将方法更改为:

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

谢谢你们,这让我走上了正轨,现在我已经解决了这个问题。

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"

的每个实例都使用相同的

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

汤姆