2012-01-05 21 views
17

當在我的網站用戶註冊,我不明白爲什麼我需要讓他「等待」 SMTP去通過,以便他得到一封激活郵件。有道異步發送ASP.NET中的電子郵件...(我是這樣做對嗎?)

我決定,我希望異步啓動這個代碼,它已經冒險。

讓我們來想象我有一個方法,如:

private void SendTheMail() { // Stuff } 

我的第一個,但..穿線。我這樣做:

Emailer mailer = new Emailer(); 
Thread emailThread = new Thread(() => mailer.SendTheMail()); 
emailThread.Start(); 

這個工程...直到我決定測試它的錯誤處理能力。我故意打破了我的web.config中的SMTP服務器地址並嘗試了它。可怕的結果是,IIS基本上BARFED在w3wp.exe上有一個未處理的異常錯誤(這是一個Windows錯誤!有多極端......)ELMAH(我的錯誤記錄器)沒有捕獲它並且IIS重新啓動,所以網站上的任何人都有他們的會話被刪除。完全不可接受的結果!

我的下一個想法,就是做異步委託一些研究。這似乎更好,因爲異常正在異步委託內處理(不像上面的線程示例)。但是,我擔心如果我做錯了,或者我正在造成內存泄漏。

下面是我在做什麼:

Emailer mailer = new Emailer(); 
AsyncMethodCaller caller = new AsyncMethodCaller(mailer.SendMailInSeperateThread); 
caller.BeginInvoke(message, email.EmailId, null, null); 
// Never EndInvoke... 

我這樣做對嗎?

+1

所以纔得到這個權利:在SendTheMail也不例外處理任何? – rene 2012-01-05 18:12:50

+1

我剛開始這樣做。在內部,我捕獲異常並像這樣調用elmah記錄器:Elmah.ErrorLog.GetDefault(null).Log(new Error(e));這就是訣竅。 – 2012-01-05 21:30:05

回答

23

有很多很好的建議,我在這裏upvoted ...如確保記住使用IDisposable(我完全不知道)。我還意識到,在另一個線程中手動捕獲錯誤是非常重要的,因爲沒有上下文 - 我一直在研究理論,我應該讓ELMAH處理所有事情。此外,進一步的探索使我意識到我忘記了在郵件消息上使用IDisposable。

作爲對Richard的迴應,雖然我看到線程解決方案可以工作(正如我的第一個示例中所示),只要我捕捉到錯誤...仍然有一些令人恐懼的事實,即IIS完全爆炸該錯誤未被捕獲。這告訴我,ASP.NET/IIS從來沒有爲你這麼做......這就是爲什麼我傾向於繼續使用.BeginInvoke /委託,因爲這不會搞砸IIS出現問題時,似乎在ASP.NET中更受歡迎。

對於ASawyer的迴應,我完全感到驚訝的是,SMTP客戶端內置了.SendAsync。我玩了一段時間的解決方案,但它似乎並沒有爲我做伎倆。儘管我可以跳過執行SendAsync的代碼的客戶端,但頁面仍然會「等待」,直到SendCompleted事件完成。我的目標是在電子郵件在後臺發送時讓用戶和頁面前進。我有一種感覺,我可能仍然在做錯事......所以如果有人來這裏,他們可能會想自己嘗試。

這裏是我的完整解決方案,我除了使用ELMAH.MVC錯誤日誌之外,還以異步方式發送100%的電子郵件。我決定去與例2的擴展版本:

public void SendThat(MailMessage message) 
{ 
    AsyncMethodCaller caller = new AsyncMethodCaller(SendMailInSeperateThread); 
    AsyncCallback callbackHandler = new AsyncCallback(AsyncCallback); 
    caller.BeginInvoke(message, callbackHandler, null); 
} 

private delegate void AsyncMethodCaller(MailMessage message); 

private void SendMailInSeperateThread(MailMessage message) 
{ 
    try 
    { 
     SmtpClient client = new SmtpClient(); 
     client.Timeout = 20000; // 20 second timeout... why more? 
     client.Send(message); 
     client.Dispose(); 
     message.Dispose(); 

     // If you have a flag checking to see if an email was sent, set it here 
     // Pass more parameters in the delegate if you need to... 
    } 
    catch (Exception e) 
    { 
     // This is very necessary to catch errors since we are in 
     // a different context & thread 
     Elmah.ErrorLog.GetDefault(null).Log(new Error(e)); 
    } 
} 

private void AsyncCallback(IAsyncResult ar) 
{ 
    try 
    { 
     AsyncResult result = (AsyncResult)ar; 
     AsyncMethodCaller caller = (AsyncMethodCaller)result.AsyncDelegate; 
     caller.EndInvoke(ar); 
    } 
    catch (Exception e) 
    { 
     Elmah.ErrorLog.GetDefault(null).Log(new Error(e)); 
     Elmah.ErrorLog.GetDefault(null).Log(new Error(new Exception("Emailer - This hacky asynccallback thing is puking, serves you right."))); 
    } 
} 
+0

在這個例子中,附件inputstream已經關閉了 – vlukham 2013-10-30 19:42:33

+1

如果你打算手動調用Dispose(而不是將你的代碼包裝在Using塊中),那麼它不應該在finally塊中嗎?無論如何,在.NET 4.5中只需使用'SendMailAsync'即可。 – 2014-09-10 07:35:13

0

如果要檢測泄漏,那麼你需要使用這樣一個分析器:

http://memprofiler.com/

我看不出有什麼錯您的解決方案,但幾乎可以向你保證,這個問題將被視爲主觀性的封閉。其他

一種選擇是使用jQuery作出AJAX調用服務器和火花電子郵件流量。這樣,用戶界面不會被鎖定。

祝你好運!

馬特

3

你使用.net SmtpClient發送電子郵件? It can send asynch messages already

編輯 - 如果Emailer mailer = new Emailer();不超過SmtpClient的包裝,這不會是非常有用我的想象。

+0

我正在嘗試這個...但頁面仍然在等待。我希望網頁立即轉到「感謝您註冊!」而電子郵件是在後臺發送的。它看起來像SendAsync的半工作,雖然...我可以跳到下一行代碼...但它會等到「SendCompleted」事件前轉到下一頁。 – 2012-01-05 18:43:08

+0

@RalphN看看Mathew的ajax基礎選項。那會爲你做詭計。 – asawyer 2012-01-05 21:21:27

3

如果您使用的是.NET的SmtpClient MAILMESSAGE和類,你應該注意的幾件事情。首先,期望發送錯誤,所以陷阱和處理它們。其次,在.Net 4中對這些類進行了一些更改,現在都實現了IDisposable(從3.5開始的MailMessage,在4.0開始的新的SmtpClient)。正因爲如此,您創建的SmtpClient和MailMessage應該使用塊進行包裝或明確處置。這是一些人沒有意識到的突變。

爲上設置更多信息請參閱本SO問題,當使用異步發送:

What are best practices for using SmtpClient, SendAsync and Dispose under .NET 4.0

+0

謝謝。我想知道人們如何同時SendAsync和Dispose? – 2012-01-05 18:43:44

+0

我認爲你需要將它置於你的SendCompleted回調中。我添加了一個鏈接給我的答案,顯示了一個例子。 – hatchet 2012-01-05 19:00:49

3

線程是不是錯誤的選項在這裏,但如果你沒有自己處理的異常,它會冒泡並使您的流程崩潰。無論你在哪個線程上執行此操作都無關緊要。

所以不是mailer.SendTheMail()試試這個:

new Thread(() => { 
    try 
    { 
    mailer.SendTheMail(); 
    } 
    catch(Exception ex) 
    { 
    // Do something with the exception 
    } 
}); 

更重要的是,如果你可以使用SmtpClient的異步功能。儘管如此,你仍然需要處理異常。

我甚至建議你看看.Net 4的新Parallet任務庫。這有額外的功能,可以讓你處理特殊情況,並與ASP.Net的線程池很好地協作。

1

我工作過同樣的問題,我的項目:

第一次嘗試Thread爲你做:
- 我寬鬆的背景下
- 異常處理問題
- 通常說,Thread都在IIS壞主意線程池

因此,我切換並嘗試使用asynchronously
- '異步'是fake在asp.net web應用程序中。它只是把隊列中的呼叫和swicth上下文

所以我做窗口服務,並通過SQL表retrive值:happy end

因此,對於快速的解決方案:從ajax側使異步調用告訴用戶fake肯定的,但是繼續你的mvc控制器發送作業

+0

通過「異步」,你的意思是我的第二個例子,是嗎? – 2012-01-05 18:44:36

1

使用此way-

private void email(object parameters) 
    { 
     Array arrayParameters = new object[2]; 
     arrayParameters = (Array)parameters; 
     string Email = (string)arrayParameters.GetValue(0); 
     string subjectEmail = (string)arrayParameters.GetValue(1); 
     if (Email != "[email protected]") 
     { 
      OnlineSearch OnlineResult = new OnlineSearch(); 
      try 
      { 
       StringBuilder str = new StringBuilder(); 
       MailMessage mailMessage = new MailMessage(); 

       //here we set the address 
       mailMessage.From = fromAddress; 
       mailMessage.To.Add(Email);//here you can add multiple emailid 
       mailMessage.Subject = ""; 
       //here we set add bcc address 
       //mailMessage.Bcc.Add(new MailAddress("[email protected]")); 
       str.Append("<html>"); 
       str.Append("<body>"); 
       str.Append("<table width=720 border=0 align=left cellpadding=0 cellspacing=5>"); 

       str.Append("</table>"); 
       str.Append("</body>"); 
       str.Append("</html>"); 
       //To determine email body is html or not 
       mailMessage.IsBodyHtml = true; 
       mailMessage.Body = str.ToString(); 
       //file attachment for this e-mail message. 
       Attachment attach = new Attachment(); 
       mailMessage.Attachments.Add(attach); 
       mailClient.Send(mailMessage); 
      } 

    } 


    protected void btnEmail_Click(object sender, ImageClickEventArgs e) 
    { 
     try 
     { 
      string To = txtEmailTo.Text.Trim(); 
      string[] parameters = new string[2]; 
      parameters[0] = To; 
      parameters[1] = PropCase(ViewState["StockStatusSub"].ToString()); 
      Thread SendingThreads = new Thread(email); 
      SendingThreads.Start(parameters); 
      lblEmail.Visible = true; 
      lblEmail.Text = "Email Send Successfully "; 
     } 
2

那麼,爲什麼沒有一個單獨的輪詢/服務與電子郵件發送專門處理?因此,允許註冊回發僅在寫入數據庫/消息隊列所需的時間內執行,並在下一個輪詢間隔之前延遲發送電子郵件。

我現在正在思考同樣的問題,我想我真的不想在服務器回發請求中發起電子郵件發送。服務網頁後面的過程應該儘可能快地向用戶反饋,您嘗試做的越多,越慢。

查看Command Query Segregation Principal(http://martinfowler.com/bliki/CQRS.html)。 Martin Fowler解釋說,在查詢部分使用的操作的命令部分可以使用不同的模型。在這種情況下,該命令將是「註冊用戶」,查詢將是激活電子郵件,使用寬鬆的比喻。相關的報價很可能是:

通過獨立的模型,我們最常用的意思是不同的對象模型,可能是在不同的邏輯過程

運行也值得一讀的是CQRS維基百科的文章(http://en.wikipedia.org/wiki/Command%E2%80%93query_separation)。很重要的一點此亮點在於:

它顯然是打算作爲編程指南,而不是良好的編碼

意義的規則,用它在您的代碼,程序執行和程序員理解將受益。這是一個很好的示例場景。

這種方法具有否定所有可能帶來的所有多線程問題和頭痛的附加益處。

+0

這真的是最好的方法,雖然不是寫入數據庫,而是使用消息隊列。雖然基本的架構是相同的。 – 2014-03-11 23:11:33

+0

@DanielMann,當然,答案更新得當。 – 2014-03-12 09:03:26

+0

這是巨大的矯枉過正。 CQRS是一個很大的話題,而不是一種應該被視爲解決方案的方法,就像發送電子郵件一樣簡單 – stimms 2015-03-21 21:32:39

5

從.NET 4.5開始,SmtpClient實現了異步等待方法 SendMailAsync。 其結果是,發送電子郵件異步是如下:

public async Task SendEmail(string toEmailAddress, string emailSubject, string emailMessage) 
{ 
    var message = new MailMessage(); 
    message.To.Add(toEmailAddress); 

    message.Subject = emailSubject; 
    message.Body = emailMessage; 

    using (var smtpClient = new SmtpClient()) 
    { 
     await smtpClient.SendMailAsync(message); 
    } 
} 
+1

不幸的是,這種方法仍然凍結網頁。 – MC9000 2016-05-13 08:38:33

+0

你使用.ConfigureAwait(false)嗎? – 2016-05-13 10:50:05

相關問題