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