Object reference not set to an instance of an object when calling a task function to send email

-3

I have the following code in a controller.

private ApplicationSignInManager _signInManager;
        private ApplicationUserManager _userManager;
        private ApplicationDbContext userDB;
        public ApplicationUserManager UserManager
        {
            get
            {
                return _userManager ?? HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
            }
            private set
            {
                _userManager = value;
            }
        }

        public async Task SendEmail(string user_id, string subject, string user_email )
        {
            await UserManager.SendEmailAsync(user_id, subject, user_email);
        }

public ActionResult Transfer(int id, int nurse_id, int ambulance_id, int driver_id, int nurse_account_id)
        {
            ...
            /////////////////////////////////
            //Send email
            string nurse_user_id = db.Nurse_Account.Where(m => m.nurse_account_id == call.nurse_account_id).Select(m => m.nurse_account_user_id).First();
            string usr = db.AspNetUsers.Where(m => m.Id == nurse_user_id).Select(m => m.Id).First();
            string usr_mail = db.AspNetUsers.Where(m => m.Id == nurse_user_id).Select(m => m.Email).First();

            SendEmail(usr, "New call", usr_mail);

            ////////////////////////////////
            ...
            return Json(new { result = "OK" }, JsonRequestBehavior.AllowGet);
        }

When I'm calling SendEmail function I get the following break error:

System.NullReferenceException
  HResult=0x80004003
  Message=Object reference not set to an instance of an object.
  Source=mscorlib
  StackTrace:
   at System.Threading.Tasks.SynchronizationContextAwaitTaskContinuation.PostAction(Object state)
   at System.Threading.Tasks.AwaitTaskContinuation.RunCallback(ContextCallback callback, Object state, Task& currentTask)
   at System.Threading.Tasks.AwaitTaskContinuation.<>c.<ThrowAsyncIfNecessary>b__18_0(Object s)
   at System.Threading.QueueUserWorkItemCallback.WaitCallback_Context(Object state)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
   at System.Threading.ThreadPoolWorkQueue.Dispatch()
   at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()

  This exception was originally thrown at this call stack:
    [External Code]
c#
asp.net-mvc
multithreading
async-await
asked on Stack Overflow Jan 15, 2021 by zinon • edited Jan 15, 2021 by zinon

1 Answer

4

The problem that you're seeing is due to request-extrinsic code. All request-extrinsic code is dangerous.

Specifically, the code is calling SendEmail but not awaiting the result. So it's trying to do a kind of "fire-and-forget". After it calls SendEmail then it sends the response back to the client, leaving SendEmail running as request-extrinsic code.

However, SendEmail is called within the ASP.NET request context, and so when it tries to resume executing after its await, it runs into the error because that ASP.NET request context is gone. The response has already been sent, so there's no more ASP.NET request context.

The best solution is to remove the request-extrinsic code. Either await the call to SendEmail or build a basic distributed architecture for asynchronous messaging. I would say just to use the await unless you must return early, in which case you need a durable queue and a worker process. The MVC action would write a message into the queue including all the email details, and then it would return the HTTP result. The worker process would read that queue and send the actual email.

Any other solution would use request-extrinsic code, which is inherently dangerous. Specifically, any request-extrinsic code may stop working without warning and without logs. "Fire and forget" literally means "forget". It's possible that work (emails in this case) may be lost. But if you're OK with that, then you can call the request-extrinsic code in a way that it does not reference the ASP.NET request context by using something like NoContext from my AsyncEx library.

answered on Stack Overflow Jan 15, 2021 by Stephen Cleary

User contributions licensed under CC BY-SA 3.0