我实际上知道自己在做什么的一些时刻;这不是其中之一。
我试着理解为什么执行特定的异步操作时代码的执行会停止。它在同步执行时起作用,在异步执行时不起作用。
这一切都发生在MVC应用程序中;
AccountController
通过AccountService
创建一个新帐户,以保持我的控制器的清洁程度。AccountService
我们做了一些像用户帐户(ASP.NET身份)这样的事情,这很好用。没什么特别的。我不是特别寻找解决方案,我对发生的事情更感到好奇。
当我在EmailSender.Send(message);
方法中执行await CreatePrimaryUser
时,account.AccountOwnerId = userId;
行永远不再执行。没有例外。看起来一切都很顺利,但方法才停止执行。
我哪里出错了?
的AccountController
[HttpPost]
[ValidateAntiForgeryToken]
[AllowAnonymous]
public ActionResult Register(AccountViewModel model)
{
if (ModelState.IsValid)
{
try
{
BusinessServiceFacade.GetAccountService(RavenMasterSession).Create(model.ToModel(), model.SubscriptionPlanId);
return Redirect(string.Format("http://{0}.localhost:6257/Admin/GettingStarted",model.Name));
}
catch(AccountTakenException)
{
ModelState.AddModelError("", "Account name already taken");
}
catch (CustomException e)
{
ModelState.AddModelError("", "Error:" + e);
}
}
return View(model);
}
GetAccountService创建
public async Task<Account> Create(Account account, string subscriptionPlanId)
{
if (IsAccountNameTaken(account.Name))
throw new AccountTakenException();
RavenMasterSession.Store(account); // register account in MasterDB
CreateTenantDatabase(account); // create tenantDB
var userId = await CreatePrimaryUser(account); // create account owner (first user)
account.AccountOwnerId = userId;
var stripeAccountResult = BusinessServiceFacade.GetStripeService(RavenMasterSession).CreateCustomer(account); // register account in Stripe
account.CustomerId = stripeAccountResult.CustomerId;
AddSubscription(account, subscriptionPlanId); // add subscription to account
return account;
}
CreatePrimaryUser
private async Task<string> CreatePrimaryUser(Account account)
{
var user = new User()
{
UserName = account.AccountOwner.Email,
FirstName = account.AccountOwner.FirstName,
LastName = account.AccountOwner.LastName,
Email = account.AccountOwner.Email
};
RavenContext.GetTenantSession().Store(user);
var result = await UserManager.CreateAsync(user);
if (result.Succeeded)
{
var message = new MailMessage();
message.To.Add("recipient@domain.com");
message.Subject = "Thank you for joining ... ";
message.Body = string.Format("Your account is available at http://{0}.localhost:6257/Admin/GettingStarted", account.Name);
await EmailSender.Send(message);
}
else
{
RavenContext.GetTenantSession().Delete(user);
var stringList = String.Concat(result.Errors.ToArray());
throw new CustomException(stringList);
}
return user.Id;
}
EmailSender
public static async Task SendSES(MailMessage message)
{
const string FROM = "from@domain.com";
message.From = new MailAddress(FROM);
const string SMTP_USERNAME = "user";
const string SMTP_PASSWORD = "pass";
const string HOST = "email-smtp.eu-west-1.amazonaws.com";
const int PORT = 587;
using (SmtpClient client = new SmtpClient(HOST, PORT))
{
client.Credentials = new System.Net.NetworkCredential(SMTP_USERNAME, SMTP_PASSWORD);
client.EnableSsl = true;
try
{
//client.Send(message);
await client.SendMailAsync(message);
}
catch (Exception ex)
{
throw ex;
}
}
}
答案 0 :(得分:3)
这是在某处混合异步同步的症状,这将导致完全如您所述的死锁。像下面一样更改你的调用代码,所以它一直是异步,否则使用async没有什么好处。像这样更改它没有问题,因为你正在使用支持动作签名的MVC。
[HttpPost]
[ValidateAntiForgeryToken]
[AllowAnonymous]
// change to async
public async Task<ActionResult> Register(AccountViewModel model)
{
if (ModelState.IsValid)
{
try
{
var service = BusinessServiceFacade.GetAccountService(RavenMasterSession);
// broken apart so you can see the await call
await service.Create(model.ToModel(), model.SubscriptionPlanId);
return Redirect(string.Format("http://{0}.localhost:6257/Admin/GettingStarted",model.Name));
}
catch(AccountTakenException)
{
ModelState.AddModelError("", "Account name already taken");
}
catch (CustomException e)
{
ModelState.AddModelError("", "Error:" + e);
}
}
return View(model);
}
重新抛出异常只写throw;
而不是throw ex;
。稍后执行操作会在异常中重置调用堆栈,这使得以后很难进行调试。要保留异常状态/调用堆栈,请使用前者(最佳实践并始终建议使用)!
try {
// something that could throw exception
} catch(Exception ex) {
// do something with the exception like logging or retry or whatever
// if the code does nothing but a rethrow then remove the whole try/catch as it has no added value
throw;
}
对于非操作方法(那些未在Mvc或Web API控制器上定义的方法),如果方法是异步方法,则应使用Async作为命名约定来后缀该方法。这使您可以轻松查看代码中的哪个位置可以使用await
或其他任务机制来执行代码。您可以看到Microsoft也使用FCL中内置的异步方法执行此操作。它不是必需的(没有什么可以强制执行),但这是一种很好的做法,因为它使代码更具可读性。
Create
变为CreateAsync
CreatePrimaryUser
变为CreatePrimaryUserAsync
SendSES
变为SendSESAsync