Ссылка на объект не установлена на экземпляр объекта при вызове функции задачи для отправки электронной почты

#c# #asp.net-mvc #multithreading #async-await

#c# #asp.net-mvc #многопоточность #асинхронное ожидание

Вопрос:

У меня есть следующий код в контроллере.

 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);
        }
 

Когда я вызываю SendEmail функцию, я получаю следующую ошибку прерывания:

 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, Taskamp; 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]
 

Комментарии:

1. Вам нужно дождаться задачи отправки электронной почты

2. Вы не показываете нам, где ошибка. Что это UserManager такое, должно ли это быть _userManager ? Вам также необходимо дождаться SendMail метода, иначе объект может быть удален до отправки почты.

3. Кроме того, пожалуйста, 1. правильно отформатируйте свой код и 2. предоставьте нам только код, который имеет отношение к вопросу. С 3k rep вы уже должны это знать.

Ответ №1:

Проблема, которую вы видите, связана с внешним кодом запроса. Весь внешний код запроса опасен.

В частности, код вызывает SendEmail , но не await выдает результат. Таким образом, он пытается сделать что-то вроде «запустить и забыть». После вызова SendEmail он отправляет ответ обратно клиенту, оставляя SendEmail выполнение как внешний код запроса.

Однако SendEmail вызывается в ASP.NET контекст запроса, и поэтому, когда он пытается возобновить выполнение после its await , он сталкивается с ошибкой, потому что ASP.NET контекст запроса исчез. Ответ уже отправлен, поэтому больше нет ASP.NET контекст запроса.

Лучшее решение — удалить внешний код запроса. Либо await вызов SendEmail , либо создание базовой распределенной архитектуры для асинхронного обмена сообщениями. Я бы сказал, просто использовать await , если вы не должны вернуться раньше, и в этом случае вам нужна длительная очередь и рабочий процесс. Действие MVC записывает сообщение в очередь, включая все данные электронной почты, а затем возвращает результат HTTP. Рабочий процесс прочитает эту очередь и отправит фактическое электронное письмо.

Любое другое решение будет использовать внешний код запроса, который по своей сути опасен. В частности, любой внешний код запроса может перестать работать без предупреждения и без журналов. «Запустить и забыть» буквально означает «забыть». Возможно, что работа (в данном случае электронные письма) может быть потеряна. Но если вас это устраивает, то вы можете вызвать внешний код запроса таким образом, чтобы он не ссылался на ASP.NET запросите контекст, используя что-то вроде NoContext из моей библиотеки AsyncEx.

Комментарии:

1. Спасибо, Стивен! Теперь мой новый код: public async Task<ActionResult> Transfer(int id, int nurse_id, int ambulance_id, int driver_id, int nurse_account_id) { ... await SendEmail(usr, "New call", usr_mail); }