2012-01-05 37 views
17

Khi người dùng đăng ký trên trang web của tôi, tôi không thấy lý do tại sao tôi cần phải "chờ" cho smtp đi qua để anh ta nhận được một email kích hoạt.Cách thích hợp để gửi email không đồng bộ trong ASP.NET ... (Tôi có làm đúng không?)

Tôi quyết định tôi muốn khởi chạy mã này không đồng bộ và đó là một cuộc phiêu lưu.

Hãy tưởng tượng tôi có một phương pháp, chẳng hạn như:

private void SendTheMail() { // Stuff } 

My đầu tiên mặc dù .. được luồng. Tôi đã làm điều này:

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

Công trình này ... cho đến khi tôi quyết định kiểm tra khả năng xử lý lỗi. Tôi cố ý phá vỡ địa chỉ máy chủ SMTP trong web.config của tôi và thử nó. Các kết quả đáng sợ là IIS về cơ bản BARFED với một lỗi ngoại lệ unhandled trên w3wp.exe (nó là một cửa sổ lỗi! Cực đoan ...) ELMAH (logger lỗi của tôi) đã không bắt nó và IIS đã được khởi động lại để bất cứ ai trên trang web đã phiên của họ bị xóa. Kết quả hoàn toàn không được chấp nhận!

Suy nghĩ tiếp theo của tôi, là thực hiện một số nghiên cứu về đại biểu không đồng bộ. Điều này dường như làm việc tốt hơn bởi vì các ngoại lệ được xử lý trong đại biểu asynch (không giống như ví dụ thread ở trên). Tuy nhiên, tôi quan tâm nếu tôi làm sai hoặc có thể tôi đang gây ra rò rỉ bộ nhớ.

Đây là những gì tôi đang làm:

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

Am Tôi làm điều này phải?

+1

Vì vậy, để có quyền này: trong SendTheMail không có xử lý ngoại lệ gì? – rene

+1

Tôi mới bắt đầu làm việc đó. Bên trong, tôi bắt ngoại lệ và gọi elmah logger như thế này: Elmah.ErrorLog.GetDefault (null) .Log (new Error (e)); Điều đó làm các trick. –

Trả lời

23

Có rất nhiều lời khuyên tốt mà tôi đã bỏ phiếu ở đây ... chẳng hạn như đảm bảo nhớ sử dụng IDisposable (tôi hoàn toàn không biết). Tôi cũng nhận ra tầm quan trọng của việc bắt lỗi bằng tay khi trong một chủ đề khác vì không có ngữ cảnh - tôi đã làm việc trên lý thuyết rằng tôi chỉ nên để ELMAH xử lý mọi thứ. Ngoài ra, thăm dò thêm làm cho tôi nhận ra tôi đã quên sử dụng IDisposable trên mailmessage, quá.

Để trả lời Richard, mặc dù tôi thấy rằng giải pháp luồng có thể hoạt động (như được đề xuất trong ví dụ đầu tiên của tôi) miễn là tôi đang gặp lỗi ... vẫn có điều gì đó đáng sợ về thực tế rằng IIS phát nổ hoàn toàn lỗi đó không bị bắt. Điều đó nói với tôi rằng ASP.NET/IIS không bao giờ có nghĩa là cho bạn để làm điều đó ... đó là lý do tại sao tôi đang nghiêng về phía tiếp tục sử dụng. BeginInvoke/đại biểu thay vì kể từ đó không mess lên IIS khi một cái gì đó đi sai và dường như được phổ biến hơn trong ASP.NET.

Để trả lời ASawyer, tôi hoàn toàn ngạc nhiên khi có một .SendAsync được tích hợp trong ứng dụng SMTP. Tôi đã chơi với giải pháp đó một thời gian, nhưng nó dường như không làm điều đó cho tôi. Mặc dù tôi có thể bỏ qua khách hàng mã mà SendAsync, trang vẫn "đợi" cho đến khi sự kiện SendCompleted được thực hiện. Mục tiêu của tôi là để người dùng và trang di chuyển về phía trước trong khi email đang được gửi trong nền. Tôi có cảm giác rằng tôi có thể vẫn đang làm điều gì đó sai trái ... vì vậy nếu ai đó đến bằng cách này họ có thể muốn thử nó.

Đây là giải pháp đầy đủ của tôi về cách tôi gửi email 100% không đồng bộ ngoài ghi nhật ký lỗi ELMAH.MVC. Tôi quyết định đi với một phiên bản mở rộng của ví dụ 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

trong dòng đầu vào đính kèm ví dụ này đã bị đóng – vlukham

+1

Nếu bạn định gọi Dispose theo cách thủ công (thay vì gói mã của bạn trong khối Sử dụng) thì không nên ở trong khối cuối cùng? Bất kể, trong .NET 4.5 chỉ cần sử dụng 'SendMailAsync' mà là awaitable. –

0

Nếu bạn muốn để phát hiện rò rỉ, sau đó bạn cần phải sử dụng một hồ sơ như thế này:

http://memprofiler.com/

tôi không thấy bất cứ điều gì sai với giải pháp của bạn, nhưng hầu như có thể đảm bảo với bạn rằng câu hỏi này sẽ được đóng lại là chủ quan.

Một tùy chọn khác là sử dụng jQuery để thực hiện cuộc gọi ajax đến máy chủ và kích hoạt luồng email. Bằng cách đó, giao diện người dùng không bị khóa.

Chúc may mắn!

Matt

3

Bạn đang sử dụng. Net SmtpClient để gửi email? It can send asynch messages already.

Chỉnh sửa - Nếu Emailer mailer = new Emailer(); không phải là trình bao bọc trên SmtpClient, điều này sẽ không hữu ích đến mức tôi tưởng tượng.

+0

Tôi đang cố gắng này ngay bây giờ ... nhưng trang vẫn chờ. Tôi muốn trang để ngay lập tức đi đến "Cảm ơn bạn đã đăng ký!" trang trong khi email được gửi trong nền. Dường như SendAsync hoạt động một nửa mặc dù ... Tôi có thể chuyển sang dòng mã tiếp theo ... nhưng nó sẽ đợi sự kiện "SendCompleted" trước khi chuyển sang trang tiếp theo. –

+0

@RalphN Hãy xem tùy chọn dựa trên ajax của Mathew. Điều đó sẽ làm điều đó cho bạn. – asawyer

3

Nếu bạn đang sử dụng các lớp SmtpClient và MailMessage .Net, bạn nên lưu ý một vài điều. Đầu tiên, mong đợi các lỗi gửi, vì vậy hãy bẫy và xử lý chúng. Thứ hai, trong .Net 4 có một số thay đổi cho các lớp này, và cả hai hiện nay thực hiện IDisposable (MailMessage từ 3.5, SmtpClient mới trong 4.0). Do đó, việc tạo SmtpClient và MailMessage của bạn nên được bao bọc bằng cách sử dụng các khối hoặc được xử lý rõ ràng. Đây là một sự thay đổi đột phá mà một số người không biết.

Xem câu hỏi này SO để biết thêm về xử lý khi sử dụng async gửi:

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

+0

Cảm ơn. Tôi tự hỏi mọi người nên SendAsync và Dispose cùng một lúc như thế nào? –

+0

Tôi nghĩ rằng bạn sẽ cần phải Vứt bỏ nó trong gọi lại SendCompleted của bạn. Tôi đã thêm một liên kết vào câu trả lời của tôi cho thấy một ví dụ. – hatchet

3

Threading không phải là lựa chọn sai ở đây, nhưng nếu bạn không xử lý một ngoại lệ cho mình, nó sẽ bong bóng lập và phá vỡ quy trình của bạn. Việc bạn làm điều đó không thành vấn đề.

Vì vậy, thay vì mailer.SendTheMail() thử này:

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

Hơn thế nữa, sử dụng khả năng không đồng bộ của SmtpClient nếu bạn có thể. Tuy nhiên, bạn vẫn sẽ cần phải xử lý các ngoại lệ.

Tôi thậm chí còn đề nghị bạn xem qua thư viện Parallet Task mới của Net4. Điều đó có thêm chức năng cho phép bạn xử lý các trường hợp đặc biệt và hoạt động tốt với hồ bơi thread của ASP.Net.

1

tôi đã làm việc cùng một vấn đề đối với dự án của tôi:

Đầu tiên cố gắng Thread như bạn làm:
- Tôi bối cảnh lỏng
- ngoại lệ xử lý vấn đề
- Thường nói, Thread là ý tưởng tồi trên IIS ThreadPool

Vì vậy, tôi chuyển đổi và thử với asynchronously:
- 'không đồng bộ' là fake trong ứng dụng web asp.net. Nó chỉ cần đặt cuộc gọi hàng đợi và swicth bối cảnh

Vì vậy, tôi làm cho cửa sổ dịch vụ và truy xuất các giá trị thông qua bảng sql: happy end

Vì vậy, để giải quyết nhanh chóng: từ ajax bên làm cho async gọi cho người sử dụng fake có, nhưng vẫn tiếp tục công việc gửi của bạn trong bộ điều khiển mvc

+0

Bởi "Không đồng bộ", bạn có nghĩa là ví dụ thứ hai của tôi, có? –

1

Sử dụng này gì theo cách

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

Vì vậy, tại sao không có một poller/dịch vụ riêng biệt mà những giao dịch độc quyền với việc gửi email? Vì vậy, cho phép đăng ký của bạn trở lại để thực hiện chỉ trong thời gian cần để ghi vào hàng đợi cơ sở dữ liệu/tin nhắn và trì hoãn việc gửi email đến khoảng thời gian bỏ phiếu tiếp theo.

Tôi đang cân nhắc vấn đề tương tự ngay bây giờ và tôi nghĩ rằng tôi thực sự không muốn thậm chí bắt đầu gửi email trong yêu cầu đăng lại máy chủ. Quá trình phục vụ các trang web nên được quan tâm để nhận được phản hồi lại cho người dùng càng sớm càng tốt, càng có nhiều công việc bạn cố làm chậm hơn.

Hãy xem Hiệu trưởng phân đoạn truy vấn lệnh (http://martinfowler.com/bliki/CQRS.html). Martin Fowler giải thích rằng các mô hình khác nhau có thể được sử dụng trong phần lệnh của một thao tác hơn là được sử dụng trong phần truy vấn. Trong trường hợp này, lệnh sẽ là "người dùng đăng ký", truy vấn sẽ là email kích hoạt, sử dụng sự tương tự lỏng lẻo. Các trích dẫn thích hợp có lẽ sẽ là:

Bằng mô hình riêng biệt chúng tôi phổ biến nhất có nghĩa là mô hình đối tượng khác nhau, có lẽ chạy trong các quá trình logic khác nhau

Cũng đáng đọc là bài viết trên Wikipedia về CQRS (http://en.wikipedia.org/wiki/Command%E2%80%93query_separation). Một điểm quan trọng mà điều này nhấn mạnh là:

nó được thiết kế rõ ràng như một hướng dẫn lập trình chứ không phải là một quy tắc cho tốt mã hóa

Ý nghĩa, sử dụng nó ở đâu mã, chương trình thực hiện của bạn và sự hiểu biết lập trình viên sẽ được hưởng lợi . Đây là một ví dụ điển hình.

Cách tiếp cận này có lợi ích bổ sung khi phủ nhận tất cả các lo ngại về luồng mufti và những cơn đau đầu mà tất cả những gì có thể mang lại.

+0

Đây thực sự là cách tiếp cận tốt nhất, mặc dù thay vì viết vào cơ sở dữ liệu, tôi muốn sử dụng hàng đợi thông báo. Kiến trúc cơ bản là như nhau, mặc dù. –

+0

@DanielMann, tất nhiên, câu trả lời được cập nhật một cách thích hợp. –

+0

Đây là quá mức cần thiết. CQRS là một chủ đề lớn và không phải là một cách tiếp cận mà nên được chào mời như là giải pháp cho một cái gì đó đơn giản như gửi một email – stimms

5

Kể từ .NET 4.5 SmtpClient thực hiện phương pháp không đồng bộ awaitable SendMailAsync. Do đó, để gửi email không đồng bộ như sau:

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

Thật không may, phương pháp này vẫn đóng băng trang web. – MC9000

+0

Bạn có sử dụng .ConfigureAwait (false) không? –

Các vấn đề liên quan