2014-05-17 48 views
1

我有以下的控制方法發送郵件異步獲取視圖結果後執行返回

public async Task<ActionResult> SendToAllUsers(SentMailToAllUsersModel model) 
    { 
     if (ModelState.IsValid) 
     { 
      var mail = MailService.SendMailToAllUsers(model.Body, model.Title); 
      await mail; 
     } 

     return View(model); 
    } 

這是調用此方法上的郵件服務

public Task SendMailToAllUsers(string content, string title) 
    { 

     var users = UserService.GetAllUsers(); 
     var mailTemplates = users.Result.AsParallel().Select(user => 
     { 

      var mailTemplate = new MastersMailTemplate(user); 
      mailTemplate.HtmlEmailTemplate = content; 
      mailTemplate.Subject = title; 
      mailTemplate.From = _fromEmail; 

      return Task.Factory.StartNew(() => MailProvider.SendEmailAsync(mailTemplate.CreateMailMessage(), new ResultDescription()).ConfigureAwait(false)); 
     }).ToArray(); 

     return Task.WhenAll(mailTemplates); 




    } 

此方法觸發郵件提供商執行此方法:

public Task<IResultDescription> SendEmailAsync(MailMessage message, IResultDescription rd) 
    { 
     // Create our SMTP Client 
     SmtpClient client = new SmtpClient(); 
     client.Host = SmtpServer; 
     client.Port = SmtpServerPort; 
     client.Credentials = new NetworkCredential(SmtpServerUsername, SmtpServerPassword); 
     client.EnableSsl = true; 

     if (AppSettings.IsInTestMode) 
     { 

      Log.Info("Test mode check: Removing all emails and replace to test"); 
      message.To.Clear(); 
      foreach (var email in AppSettings.DefaultTestEmail) 
      { 
       message.To.Add(new MailAddress(email)); 
      } 
     } 

     client.Timeout = 10; 
     Log.Info("Sending Email to" + message.To.FirstOrDefault()); 
     var task = Task.Run(async() => 
     { 
      try{ 
       client.SendCompleted += (s, e) => 
       { 
        client.Dispose(); 
        message.Dispose(); 
       }; 
       await client.SendAsync(message); 
       rd.Success = true; 
       return rd; 
      } 
      catch (Exception e) 
      { 
       Log.Error("Email not send"); 
       rd.Success = false; 
       if (rd.Errors == null) 
       { 

        IList<string> errors = new List<string>(); 
        errors.Add(e.Message); 
        rd.Errors = errors; 
       } 
       else 
       { 
        rd.Errors.Add(e.Message); 
       } 

       return rd; 
      } 
     }); 

     return task; 




    } 

問題是結果視圖返回在任何發送郵件之前清除。
控制器不會等待直到發送所有郵件。

如何確保控制器僅在郵件服務中的所有任務完成時才繼續執行?

+0

爲什麼你要做'await client.SendAsync(...)',如果你只是做'client.Send(...)'? – elgonzo

+0

謝謝elgonzo,這個工作! – Identity

+0

請注意,* SendCompleted *事件未針對'client.Send(...)'觸發。然而,這不是一個真正的問題,因爲你可以把你的* SendCompleted *處理程序的代碼放在'client.Send(...)'後面(注意,每當發生異常時,那些.Dispose()調用不是正在製作) – elgonzo

回答

0

我認爲問題出在您的SendMailToAllUsers方法中。我認爲你需要撥打awaitMailProvider.SendEmailAsync。如果您不這樣做,那麼一旦該方法執行,由Task.Factory.StartNew開始的任務將被視爲完成。由於該方法實際上是異步的,它只會啓動操作,不會等待其完成。如果你await應該解決問題的結果。

你的代碼更改爲:

public Task SendMailToAllUsers(string content, string title) 
    { 
     var users = UserService.GetAllUsers(); 
     var mailTemplates = users.Result.AsParallel().Select(user => 
     { 

      var mailTemplate = new MastersMailTemplate(user); 
      mailTemplate.HtmlEmailTemplate = content; 
      mailTemplate.Subject = title; 
      mailTemplate.From = _fromEmail; 

      // Await the result of the lambda expression 
      return Task.Factory.StartNew(() => await MailProvider.SendEmailAsync(mailTemplate.CreateMailMessage(), new ResultDescription()).ConfigureAwait(false)); 
     }).ToArray(); 

     return Task.WhenAll(mailTemplates); 
    } 
2

一般來說,不要使用ASP.NET Task.RunTask.Factory.StartNewParallel,或PLINQ。總有一個更好的方法。在這種情況下,只要使用asyncawait

public async Task SendMailToAllUsersAsync(string content, string title) 
{ 
    var users = await UserService.GetAllUsersAsync(); 
    var mailTemplates = users.AsParallel().Select(user => 
    { 
    var mailTemplate = new MastersMailTemplate(user); 
    mailTemplate.HtmlEmailTemplate = content; 
    mailTemplate.Subject = title; 
    mailTemplate.From = _fromEmail; 
    return MailProvider.SendEmailAsync(mailTemplate.CreateMailMessage()); 
    }).ToArray(); 
    return await Task.WhenAll(mailTemplates); 
} 

同樣,對於你內心的方法:

public Task<IResultDescription> SendEmailAsync(MailMessage message, IResultDescription rd) 
{ 
    using (SmtpClient client = new SmtpClient()) 
    using (message) 
    { 
    client.Host = SmtpServer; 
    client.Port = SmtpServerPort; 
    client.Credentials = new NetworkCredential(SmtpServerUsername, SmtpServerPassword); 
    client.EnableSsl = true; 

    if (AppSettings.IsInTestMode) 
    { 
     Log.Info("Test mode check: Removing all emails and replace to test"); 
     message.To.Clear(); 
     foreach (var email in AppSettings.DefaultTestEmail) 
     { 
     message.To.Add(new MailAddress(email)); 
     } 
    } 

    client.Timeout = 10; 
    Log.Info("Sending Email to" + message.To.FirstOrDefault()); 
    try 
    { 
     await client.SendAsync(message); 
     rd.Success = true; 
    } 
    catch (Exception e) 
    { 
     Log.Error("Email not send"); 
     rd.Success = false; 
     if (rd.Errors == null) 
     { 
     IList<string> errors = new List<string>(); 
     errors.Add(e.Message); 
     rd.Errors = errors; 
     } 
     else 
     { 
     rd.Errors.Add(e.Message); 
     } 
    } 
    return rd; 
    } 
} 

記住,async使事情容易。如果async代碼過於複雜,請檢查A Better Way。我的博客上有一個async intro,您可能會覺得有用。

+0

嗨,先生,對於重振這個主題感到抱歉。但我有問題。當我運行我的程序是localhost它工作正常,但是當我託管我的網站的郵件發送,但SendMailAsync永遠不會返回。這是一個死鎖問題嗎?我只是新的這種東西,我希望你能幫助我先生,先謝謝你。這裏是我的控制器:http://pastie.org/10907763試圖編輯'await smtp.SendMailAsync(message)'到'await smtp.SendMailAsync(message).ConfigureAwait(false);'但它仍然不會返回。真的希望你能幫我先生。謝謝。有一個g'day。 :) –

+0

我看了一下代碼,沒有發現任何東西跳出來。 要檢查的一點是,你的目標是.NET 4.5或更高版本,*和*已經在你的'web.config'中將 'targetFramework'設置爲'4.5'或更高。代碼本身 看起來不錯,應該不會造成死鎖。如果它不是4。5定位問題,那麼我建議您將問題減少到重現該問題所需的最小代碼量,並將其作爲自己的問題發佈。 –