Які найкращі практики використання SmtpClient, SendAsync та Dispose під .NET 4.0


116

Я трохи здивований, як керувати SmtpClient тепер, коли він є одноразовим, особливо якщо я телефоную за допомогою SendAsync. Імовірно, я не повинен закликати розпоряджатися, поки SendAsync не завершиться. Але чи повинен я коли-небудь це називати (наприклад, використовуючи "використання"). Сценарій - це послуга WCF, яка періодично надсилає електронні листи, коли здійснюються дзвінки. Більшість обчислень проходить швидко, але надсилання електронної пошти може зайняти секунду або близько того, тому Async буде кращим.

Чи потрібно створювати новий SmtpClient кожного разу, коли я надсилаю пошту? Чи варто створити його для всього WCF? Довідка!

Оновлення Якщо це має значення, кожен електронний лист завжди налаштовується для користувача. WCF розміщується на Azure, а Gmail використовується як електронна пошта.


1
Дивіться цю публікацію про більшу картину про те, як поводитися з IDisposable та async: stackoverflow.com/questions/974945/…
Кріс Хаас

Відповіді:


139

Примітка: .NET 4.5 SmtpClient реалізує async awaitableметод SendMailAsync. Для нижчих версій використовуйте, SendAsyncяк описано нижче.


Ви завжди повинні розпоряджатися IDisposableекземплярами якомога раніше. У випадку з асинхронними дзвінками це повернення дзвінка після надсилання повідомлення.

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);

Це трохи дратує те, SendAsyncщо не приймає зворотний дзвінок.


чи не повинен останній рядок "чекати"?
niico

20
Ніякого цього коду раніше awaitне було написано . Це традиційний зворотний дзвінок з використанням обробників подій. awaitслід використовувати, якщо використовуєте новіший SendMailAsync.
TheCodeKing

3
SmtpException: Помилка надсилання пошти .--> System.InvalidOperationException: Асинхронна операція наразі не може бути запущена. Асинхронні операції можуть бути запущені лише в межах асинхронного обробника або модуля або під час певних подій у життєвому циклі сторінки. Якщо цей виняток стався під час виконання сторінки, переконайтеся, що Сторінка позначена <% @ Page Async = "true"%>. Цей виняток також може вказувати на спробу виклику методу "асинхронізація", який, як правило, не підтримується під час обробки запиту ASP.NET. Натомість асинхронний метод повинен повернути завдання, а абонент повинен його чекати.
Mrchief

1
Чи безпечно це вказати nullяк другий параметр SendAsync(...)?
Jocull

167

Оригінальне запитання було задано .NET 4, але якщо він допомагає, як .NET 4.5 SmtpClient реалізує метод очікування async SendMailAsync.

Як результат, надсилати електронну пошту асинхронно таким чином:

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);
        }
    }
}

Краще уникати використання методу SendAsync.


Чому краще цього уникати? Я думаю, це залежить від вимог.
Джовен

14
SendMailAsync () - так чи інакше - обгортка навколо методу SendAsync (). Асинхрон / очікування - це спосіб акуратніший і елегантніший. Це дозволило б досягти точно таких же вимог.
Борис Ліпшиц

2
@RodHartzell ви завжди можете використовувати .ContinueWith ()
Борис Ліпшиц

2
Чи краще використовувати - чи розпоряджатися - чи немає різної практичної різниці? Чи не можливо в тому останньому блоці "використання", який smtpClient міг бути утилізований до того, як SendMailAsync виконає?
niico

6
MailMessageтакож слід утилізувати.
TheCodeKing

16

Взагалі об’єкти, що не використовуються, повинні бути утилізовані якнайшвидше; реалізація IDisposable на об'єкті призначена для повідомлення про те, що у відповідного класу є дорогі ресурси, які слід детерміновано звільнити. Однак якщо створення цих ресурсів коштує дорого і вам потрібно сконструювати багато цих об'єктів, можливо, краще (з розумом виконання) зберігати один екземпляр у пам’яті та використовувати його повторно. Є лише один спосіб дізнатися, чи це має значення: профілі!

Re: розпорядження та Async: usingочевидно, ви не можете використовувати . Замість цього ви зазвичай розпоряджаєтесь об'єктом у події SendCompleted:

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

6

Гаразд, старе питання, яке я знаю. Але я натрапив на це сам, коли мені потрібно було реалізувати щось подібне. Я просто хотів поділитися деяким кодом.

Я повторюю декілька SmtpClients, щоб надіслати кілька повідомлень асинхронно. Моє рішення схоже на TheCodeKing, але я розміщую натомість об'єкт зворотного виклику. Я також передаю MailMessage як userToken, щоб отримати його у події SendCompleted, щоб я також міг зателефонувати розпоряджатися цим. Подобається це:

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
Чи найкраща практика створити новий SmtpClient для кожного електронного листа, що надсилається?
Мартін Колл

1
Так, для асинхронної відправки, поки ви розпоряджаєтесь клієнтом у зворотному
дзвінку

1
Дякую! і лише заради короткого пояснення: www.codefrenzy.net/2012/01/30/how-asynchronous-is-smtpclient-sendasync
Martín Coll

1
Це одна з найпростіших і точних відповідей, яку я знайшов у stackoverflow для функції smtpclient.sendAsync та пов'язаного з цим розпорядження. Я написав асинхронну бібліотеку масової відправки пошти. Оскільки я надсилаю повідомлення 50+ кожні кілька хвилин, тому виконання методу утилізації було для мене дуже важливим кроком. Цей код точно допоміг мені досягти цього. Я відповім у випадку, якщо я знайшов деякі помилки в цьому коді під час роботи з декількома потоками.
vibs2006

1
Я можу сказати, що це не гарний підхід, коли ви надсилаєте 100+ електронних листів у циклі, якщо у вас немає можливості налаштувати сервер обміну (якщо ви використовуєте). Сервер може кинути виняток, як 4.3.2 The maximum number of concurrent connections has exceeded a limit, closing trasmission channel. Натомість спробуйте використовувати лише один екземплярSmtpClient
ibubi

6

Ви можете зрозуміти, чому особливо важливо утилізувати SmtpClient наступним коментарем:

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();

У моєму сценарії надсилання декількох листів за допомогою Gmail без розпорядження клієнта я отримував:

Повідомлення: Служба недоступна, закриваючи канал передачі. Відповідь сервера була: 4.7.0 Тимчасова системна проблема. Повторіть спробу пізніше (WS). oo3sm17830090pdb.64 - gsmtp


1
Дякуємо, що поділилися вашим винятком тут, оскільки я надсилав SMTP-клієнтів, поки не розпоряджався. Хоча я використовую власний сервер SMTP, але завжди слід враховувати хорошу практику програмування. З огляду на вашу помилку, я отримав застереження і виправляю свій код, щоб включити функції розпорядження, щоб забезпечити надійність платформи.
vibs2006
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.