I am fairly new to threading and C#. I'm trying to implement Identity, I run into a problem: when I register a user and send the confirmation link, clicking on the link causes an exception:
I try to do my best to simplify my code to explain problem.
so I have AccountController
in my Controllers of MVC project:
namespace SolwayOrder.Controllers {
public class AccountController: Controller {
private readonly ApplicationUserManager _manager;
private readonly SignInManager < IdentityUser, string > _signIn;
private readonly RoleManager < IdentityRole, int > _roleManager;
public AccountController(
ApplicationUserManager manager,
SignInManager < IdentityUser, string > signIn,
RoleManager < IdentityRole, int > roleManager
) {
_manager = manager;
_signIn = signIn;
_roleManager = roleManager;
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task < ActionResult > Register(RegisterModel model, string returnUrl) {
if (ModelState.IsValid) {
var user = new IdentityUser(model.Email);
var result = await _manager.CreateAsync(user, model.Password);
if (result.Succeeded) {
var code = await _manager.GenerateEmailConfirmationTokenAsync(model.Email); var callbackUrl = Url.Action(
"ConfirmEmail", "Account",
new {
userId = model.Email, code = code
},
protocol: Request.Url.Scheme);
await _manager.SendEmailAsync(model.Email,
"Confirm your account",
"Please confirm your account by clicking this link: <a href=\"" + callbackUrl + "\">link</a>");
return Content("Please confirm your account. Link was sent to " + model.Email);
}
}
return View();
}
[HttpGet]
public async Task < ActionResult > ConfirmEmail(string userId, string code) {
if (userId == null || code == null) return View("Register"); //error
var result = await _manager.ConfirmEmailAsync(userId, code);
if (result.Succeeded) {
return View();
}
return View("Register");
}
}
}
The problem arise when I click the link which I got on Email:
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.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.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.
Source Error:
Line 29:
Line 30: public async Task<User> FindByUserNameAsync(string username) {
Line 31: return await Set.FirstOrDefaultAsync(x => x.Email == username);
Line 32: }
Line 33:
Source File: C:\Users\Solway\documents\visual studio 2017\Projects\SolwayOrder\SolwayOrder.Repo\Repositories\UserRepository.cs Line: 31
I modified it so everywhere for asynchronous methods, where Task is returned, I have async and await.
sorry couldn't simplify the question more, I think this is common pattern and should be understandable. if not here's a link to full project: DropBox Link To project
full stack:
[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.]
System.Data.Entity.Internal.ThrowingMonitor.EnsureNotEntered() +72
System.Data.Entity.Core.Objects.ObjectQuery`1.System.Data.Entity.Infrastructure.IDbAsyncEnumerable<T>.GetAsyncEnumerator() +22
System.Data.Entity.Infrastructure.<FirstOrDefaultAsync>d__25`1.MoveNext() +114
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +99
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +58
System.Runtime.CompilerServices.TaskAwaiter`1.GetResult() +28
SolwayOrder.Repo.<FindByUserNameAsync>d__5.MoveNext() in C:\Users\Solway\documents\visual studio 2017\Projects\SolwayOrder\SolwayOrder.Repo\Repositories\UserRepository.cs:31
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +99
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +58
System.Runtime.CompilerServices.TaskAwaiter`1.GetResult() +28
SolwayOrder.Service.<FindUserByNameAsync>d__7.MoveNext() in C:\Users\Solway\documents\visual studio 2017\Projects\SolwayOrder\SolwayOrder.Service\Security.cs:40
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +99
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +58
System.Runtime.CompilerServices.TaskAwaiter`1.GetResult() +28
SolwayOrder.Identity.<FindByNameAsync>d__5.MoveNext() in C:\Users\Solway\documents\visual studio 2017\Projects\SolwayOrder\SolwayOrder\Identity\UserStore.cs:46
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +99
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +58
Microsoft.AspNet.Identity.CultureAwaiter`1.GetResult() +59
Microsoft.AspNet.Identity.<ValidateUserName>d__4.MoveNext() +589
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +99
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +58
Microsoft.AspNet.Identity.<ValidateAsync>d__0.MoveNext() +293
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +99
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +58
Microsoft.AspNet.Identity.<UpdateAsync>d__5.MoveNext() +295
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +99
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +58
Microsoft.AspNet.Identity.CultureAwaiter`1.GetResult() +59
Microsoft.AspNet.Identity.<ConfirmEmailAsync>d__c5.MoveNext() +1100
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +99
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +58
System.Runtime.CompilerServices.TaskAwaiter`1.GetResult() +28
SolwayOrder.Controllers.<ConfirmEmail>d__8.MoveNext() in C:\Users\Solway\documents\visual studio 2017\Projects\SolwayOrder\SolwayOrder\Controllers\AccountController.cs:88
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +99
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +58
System.Web.Mvc.Async.TaskAsyncActionDescriptor.EndExecute(IAsyncResult asyncResult) +97
System.Web.Mvc.Async.<>c__DisplayClass37.<BeginInvokeAsynchronousActionMethod>b__36(IAsyncResult asyncResult) +17
System.Web.Mvc.Async.WrappedAsyncResult`1.CallEndDelegate(IAsyncResult asyncResult) +10
System.Web.Mvc.Async.WrappedAsyncResultBase`1.End() +49
System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethod(IAsyncResult asyncResult) +32
System.Web.Mvc.Async.AsyncInvocationWithFilters.<InvokeActionMethodFilterAsynchronouslyRecursive>b__3d() +50
System.Web.Mvc.Async.<>c__DisplayClass46.<InvokeActionMethodFilterAsynchronouslyRecursive>b__3f() +228
System.Web.Mvc.Async.<>c__DisplayClass33.<BeginInvokeActionMethodWithFilters>b__32(IAsyncResult asyncResult) +10
System.Web.Mvc.Async.WrappedAsyncResult`1.CallEndDelegate(IAsyncResult asyncResult) +10
System.Web.Mvc.Async.WrappedAsyncResultBase`1.End() +49
System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethodWithFilters(IAsyncResult asyncResult) +34
System.Web.Mvc.Async.<>c__DisplayClass2b.<BeginInvokeAction>b__1c() +26
System.Web.Mvc.Async.<>c__DisplayClass21.<BeginInvokeAction>b__1e(IAsyncResult asyncResult) +100
System.Web.Mvc.Async.WrappedAsyncResult`1.CallEndDelegate(IAsyncResult asyncResult) +10
System.Web.Mvc.Async.WrappedAsyncResultBase`1.End() +49
System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeAction(IAsyncResult asyncResult) +27
System.Web.Mvc.Controller.<BeginExecuteCore>b__1d(IAsyncResult asyncResult, ExecuteCoreState innerState) +13
System.Web.Mvc.Async.WrappedAsyncVoid`1.CallEndDelegate(IAsyncResult asyncResult) +29
System.Web.Mvc.Async.WrappedAsyncResultBase`1.End() +49
System.Web.Mvc.Controller.EndExecuteCore(IAsyncResult asyncResult) +36
System.Web.Mvc.Controller.<BeginExecute>b__15(IAsyncResult asyncResult, Controller controller) +12
System.Web.Mvc.Async.WrappedAsyncVoid`1.CallEndDelegate(IAsyncResult asyncResult) +22
System.Web.Mvc.Async.WrappedAsyncResultBase`1.End() +49
System.Web.Mvc.Controller.EndExecute(IAsyncResult asyncResult) +26
System.Web.Mvc.Controller.System.Web.Mvc.Async.IAsyncController.EndExecute(IAsyncResult asyncResult) +10
System.Web.Mvc.MvcHandler.<BeginProcessRequest>b__5(IAsyncResult asyncResult, ProcessRequestState innerState) +21
System.Web.Mvc.Async.WrappedAsyncVoid`1.CallEndDelegate(IAsyncResult asyncResult) +29
System.Web.Mvc.Async.WrappedAsyncResultBase`1.End() +49
System.Web.Mvc.MvcHandler.EndProcessRequest(IAsyncResult asyncResult) +28
System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.EndProcessRequest(IAsyncResult result) +9
System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +9987157
System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +155
what really surprises me is that if I set breakpoint on EmailConfirm Controller, and go step by step, everything goes smooth and perfect, no exception.
In your repository classes (
SolwayOrder.Repo.Repository
,SolwayOrder.Repo.UserRepository
,SolwayOrder.Repo.UnitOfWork
) you keep Singletone instance ofAppDbContext
in private field and use it in all operations.If several concurrent requests are executing, you catch that exception with clear message 'A second operation started on this context before a previous asynchronous operation completed'.
As a rule you should create instance of a context in each operation and dispose it after the use. Do not be afraid of performance degradation due to connection opening for every context creation. Entity Framework is built over ADO.NET that uses connection pooling. Performance should not degrade comparing to Singleton context.
For example your
SaveChangesAsync
methodshould be rewritten as:
Note, that you can't keep the method non-async as previously. As far as your context is disposed in the method, you should await for SaveChangesAsync operation to complete.
Just to be clear, aside from "adding
await
", you also need toasync
IdentityUser
The standard code would be
Notice that you should use "
async
all the way", so also yourFindUserByNameAsync
sould read