Thực tiễn tốt nhất để sử dụng SmtpClient, SendAsync và Vứt bỏ trong .NET 4.0 là gì


116

Bây giờ tôi hơi bối rối về cách quản lý SmtpClient vì nó chỉ dùng một lần, đặc biệt nếu tôi thực hiện cuộc gọi bằng SendAsync. Có lẽ tôi không nên gọi Dispose cho đến khi SendAsync hoàn tất. Nhưng tôi có nên gọi nó không (ví dụ: sử dụng "bằng cách sử dụng"). Kịch bản là một dịch vụ WCF gửi email theo định kỳ khi các cuộc gọi được thực hiện. Hầu hết các tính toán đều nhanh, nhưng việc gửi email có thể mất một giây hoặc lâu hơn, vì vậy Async sẽ thích hợp hơn.

Tôi có nên tạo SmtpClient mới mỗi lần tôi gửi thư không? Tôi có nên tạo một cái cho toàn bộ WCF không? Cứu giúp!

Cập nhật Trong trường hợp nó tạo ra sự khác biệt, mỗi email luôn được tùy chỉnh cho người dùng. WCF được lưu trữ trên Azure và Gmail được sử dụng làm người gửi thư.


1
Xem bài đăng này về bức tranh lớn hơn về cách xử lý IDis Dùng và không đồng bộ: stackoverflow.com/questions/974945/ Kẻ
Chris Haas

Câu trả lời:


139

Lưu ý: .NET 4.5 SmtpClient thực hiện async awaitablephương thức SendMailAsync. Đối với các phiên bản thấp hơn, sử dụng SendAsyncnhư mô tả dưới đây.


Bạn nên luôn luôn xử lý các IDisposabletrường hợp sớm nhất có thể. Trong trường hợp các cuộc gọi không đồng bộ, đây là cuộc gọi lại sau khi tin nhắn được gửi.

var message = new MailMessage("from", "to", "subject", "body"))
var client = new SmtpClient("host");
client.SendCompleted += (s, e) => {
                           client.Dispose();
                           message.Dispose();
                        };
client.SendAsync(message, null);

Có một chút khó chịu khi SendAsynckhông chấp nhận cuộc gọi lại.


dòng cuối cùng có 'chờ đợi' không?
niico

19
Không có mã này đã được viết trước khi awaitcó sẵn. Đây là một cuộc gọi lại truyền thống sử dụng xử lý sự kiện. awaitnên được sử dụng nếu sử dụng mới hơn SendMailAsync.
TheCodeKing

3
SmtpException: Không gửi được thư .--> System.InvalidOperationException: Một hoạt động không đồng bộ có thể được bắt đầu tại thời điểm này. Các hoạt động không đồng bộ chỉ có thể được bắt đầu trong một trình xử lý hoặc mô đun không đồng bộ hoặc trong các sự kiện nhất định trong vòng đời của Trang. Nếu ngoại lệ này xảy ra trong khi thực thi Trang, hãy đảm bảo rằng Trang được đánh dấu <% @ Trang Async = "true"%>. Ngoại lệ này cũng có thể cho thấy nỗ lực gọi phương thức "async void", thường không được hỗ trợ trong quá trình xử lý yêu cầu ASP.NET. Thay vào đó, phương thức không đồng bộ sẽ trả về một Tác vụ và người gọi sẽ chờ nó.
Mrchief

1
Có an toàn để cung cấp nullnhư là tham số thứ hai SendAsync(...)không?
vui vẻ

166

Câu hỏi ban đầu được hỏi về .NET 4, nhưng nếu nó giúp .NET 4.5 SmtpClient thực hiện phương thức không đồng bộ đang chờ SendMailAsync.

Do đó, để gửi email không đồng bộ như sau:

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

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

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

Tốt hơn hết là tránh sử dụng phương thức SendAsync.


Tại sao tốt hơn là tránh nó? Tôi nghĩ rằng nó phụ thuộc vào các yêu cầu.
Jowen

14
SendMailAsync () là một trình bao bọc xung quanh phương thức SendAsync (). async / await là cách gọn gàng và thanh lịch hơn. Nó sẽ đạt được chính xác các yêu cầu tương tự.
Boris Lipschitz

2
@RodHartzell bạn luôn có thể sử dụng .ContinueWith ()
Boris Lipschitz

2
Là tốt hơn để sử dụng bằng cách sử dụng - hoặc loại bỏ - hoặc không có sự khác biệt thực tế? Không thể trong khối 'sử dụng' cuối cùng mà smtpClient có thể được xử lý trước khi SendMailAsync thực thi?
niico

6
MailMessagecũng nên được xử lý.
TheCodeKing

16

Nói chung, các đối tượng IDis Dùng nên được xử lý càng sớm càng tốt; triển khai IDis Dùng trên một đối tượng nhằm truyền đạt thực tế rằng lớp đang nói đến chứa các tài nguyên đắt tiền cần được phát hành một cách xác định. Tuy nhiên, nếu việc tạo các tài nguyên đó là tốn kém và bạn cần xây dựng nhiều đối tượng này, thì có thể tốt hơn (hiệu suất khôn ngoan) để giữ một cá thể trong bộ nhớ và sử dụng lại nó. Chỉ có một cách để biết nếu điều đó làm cho bất kỳ sự khác biệt: hồ sơ nó!

Re: dispose và Async: usingrõ ràng bạn không thể sử dụng . Thay vào đó, bạn thường loại bỏ đối tượng trong sự kiện SendCompleted:

var smtpClient = new SmtpClient();
smtpClient.SendCompleted += (s, e) => smtpClient.Dispose();
smtpClient.SendAsync(...);

6

Ok, câu hỏi cũ tôi biết. Nhưng tôi đã vấp phải điều này khi tôi cần thực hiện một cái gì đó tương tự. Tôi chỉ muốn chia sẻ một số mã.

Tôi đang lặp đi lặp lại qua một số SmtpCl Client để gửi một số thư không đồng bộ. Giải pháp của tôi tương tự như TheCodeKing, nhưng thay vào đó tôi lại xử lý đối tượng gọi lại. Tôi cũng đang chuyển MailMessage với tư cách là userToken để có được nó trong sự kiện SendCompleted để tôi cũng có thể gọi vứt bỏ nó. Như thế này:

foreach (Customer customer in Customers)
{
    SmtpClient smtpClient = new SmtpClient(); //SmtpClient configuration out of this scope
    MailMessage message = new MailMessage(); //MailMessage configuration out of this scope

    smtpClient.SendCompleted += (s, e) =>
    {
        SmtpClient callbackClient = s as SmtpClient;
        MailMessage callbackMailMessage = e.UserState as MailMessage;
        callbackClient.Dispose();
        callbackMailMessage.Dispose();
    };

    smtpClient.SendAsync(message, message);
}

2
Đây có phải là cách tốt nhất để tạo một SmtpClient mới cho mỗi email để gửi không?
Martín Coll

1
Có, đối với việc gửi không đồng bộ, miễn là bạn loại bỏ ứng dụng khách trong cuộc gọi lại ...
jmelhus

1
cảm ơn! và chỉ vì một lời giải thích ngắn gọn: www.codefrenzy.net/2012/01/30/how-asynncous-is-smtpclient-sendasync
Martín Coll

1
Đây là một trong những câu trả lời đơn giản và chính xác nhất mà tôi tìm thấy trên stackoverflow cho hàm smtpclient.sendAsync và xử lý xử lý liên quan của nó. Tôi đã viết một thư viện gửi thư hàng loạt không đồng bộ. Khi tôi gửi hơn 50 tin nhắn cứ sau vài phút, do đó thực thi phương thức xử lý là một bước rất quan trọng đối với tôi. Mã này chính xác đã giúp tôi đạt được điều đó. Tôi sẽ trả lời trong trường hợp tôi tìm thấy một số lỗi trong mã này trong môi trường đa luồng.
vibs2006

1
Tôi có thể nói rằng đó không phải là một cách tiếp cận tốt khi bạn đang gửi hơn 100 email trong một vòng lặp trừ khi bạn có khả năng định cấu hình máy chủ trao đổi (nếu bạn sử dụng). Máy chủ có thể ném ngoại lệ như thế nào 4.3.2 The maximum number of concurrent connections has exceeded a limit, closing trasmission channel. Thay vào đó, hãy thử chỉ sử dụng một phiên bản củaSmtpClient
ibubi

6

Bạn có thể thấy lý do tại sao đặc biệt quan trọng để loại bỏ SmtpClient bằng cách nhận xét sau:

public class SmtpClient : IDisposable
   // Summary:
    //     Sends a QUIT message to the SMTP server, gracefully ends the TCP connection,
    //     and releases all resources used by the current instance of the System.Net.Mail.SmtpClient
    //     class.
    public void Dispose();

Trong kịch bản của tôi gửi nhiều thư bằng Gmail mà không xử lý ứng dụng khách, tôi đã sử dụng để nhận:

Tin nhắn: Dịch vụ không khả dụng, đóng kênh truyền. Phản hồi của máy chủ là: 4.7.0 Sự cố hệ thống tạm thời. Hãy thử lại sau (WS). oo3sm17830090pdb.64 - GSMtp


1
Cảm ơn vì đã chia sẻ ngoại lệ của bạn ở đây vì tôi đã gửi các Khách hàng SMTP mà không xử lý cho đến nay. Mặc dù tôi đang sử dụng Máy chủ SMTP của riêng mình nhưng nên luôn luôn cân nhắc thực hành lập trình tốt. Nhìn từ lỗi của bạn, giờ tôi đã có cảnh báo và sẽ sửa lỗi mã của mình để bao gồm các hàm xử lý để đảm bảo độ tin cậy của nền tảng.
Vibs2006
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.