Каковы лучшие практики использования SmtpClient, SendAsync и Dispose в .NET 4.0

116

Я немного озадачен тем, как управлять SmtpClient теперь, когда он одноразовый, особенно если я звоню с помощью SendAsync. По-видимому, мне не следует вызывать Dispose до завершения SendAsync. Но должен ли я когда-нибудь называть это (например, используя "использование"). Сценарий представляет собой службу WCF, которая периодически рассылает электронную почту при совершении звонков. Большая часть вычислений выполняется быстро, но отправка электронной почты может занять секунду или около того, поэтому предпочтительнее использовать Async.

Следует ли мне создавать новый SmtpClient каждый раз при отправке почты? Должен ли я создать его для всего WCF? Помогите!

Обновление. Если это имеет значение, каждое электронное письмо всегда настраивается под пользователя. WCF размещен в Azure, а Gmail используется в качестве почтовой программы.

tofutim
источник
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не принимает обратный вызов.

TheCodeKing
источник
не должно ли в последней строке быть «ожидание»?
niico
20
Этот код не был написан до того, как awaitбыл доступен. Это традиционный обратный вызов с использованием обработчиков событий. awaitследует использовать при использовании более новой версии SendMailAsync.
TheCodeKing
3
SmtpException: сбой при отправке почты. -> System.InvalidOperationException: асинхронная операция не может быть запущена в это время. Асинхронные операции можно запускать только в асинхронном обработчике или модуле или во время определенных событий в жизненном цикле страницы. Если это исключение произошло при выполнении страницы, убедитесь, что страница помечена как <% @ Page Async = "true"%>. Это исключение также может указывать на попытку вызвать метод «async void», который обычно не поддерживается при обработке запросов ASP.NET. Вместо этого асинхронный метод должен вернуть задачу, а вызывающий должен ее дождаться.
Mrchief 02
1
Безопасно ли указывать nullв качестве второго параметра SendAsync(...)?
jocull 08
167

Исходный вопрос был задан для .NET 4, но если он помогает, начиная с .NET 4.5, SmtpClient реализует метод async awaitable 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 (). async / await намного аккуратнее и элегантнее. Это будет соответствовать точно таким же требованиям.
Борис Липшиц
2
@RodHartzell, вы всегда можете использовать .ContinueWith ()
Борис Липшиц,
2
Что лучше: использовать - или утилизировать - или нет практической разницы? Разве в этом последнем блоке using нельзя удалить smtpClient до выполнения SendMailAsync?
niico
6
MailMessageтакже должен быть удален.
TheCodeKing
16

В общем, объекты IDisposable следует удалять как можно скорее; реализация IDisposable на объекте предназначена для сообщения того факта, что рассматриваемый класс содержит дорогие ресурсы, которые должны быть детерминированно освобождены. Однако, если создание этих ресурсов является дорогостоящим и вам нужно создать множество этих объектов, может быть лучше (с точки зрения производительности) сохранить один экземпляр в памяти и повторно использовать его. Есть только один способ узнать, имеет ли это значение: профилировать это!

Re: утилизация и Async: usingочевидно, вы не можете использовать . Вместо этого вы обычно удаляете объект в событии SendCompleted:

var smtpClient = new SmtpClient();
smtpClient.SendCompleted += (s, e) => smtpClient.Dispose();
smtpClient.SendAsync(...);
jeroenh
источник
6

Хорошо, старый вопрос я знаю. Но я сам наткнулся на это, когда мне нужно было реализовать что-то подобное. Я просто хотел поделиться кодом.

Я перебираю несколько SmtpClients, чтобы отправлять несколько писем асинхронно. Мое решение похоже на TheCodeKing, но вместо этого я удаляю объект обратного вызова. Я также передаю MailMessage как userToken, чтобы получить его в событии SendCompleted, чтобы я мог также вызвать dispose для этого. Как это:

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);
}
jmelhus
источник
2
Лучше всего создавать новый SmtpClient для каждого отправляемого электронного письма?
Мартин Колл
1
Да, для асинхронной отправки, если вы избавляетесь от клиента в
обратном вызове
1
Спасибо! и просто для краткого пояснения: www.codefrenzy.net/2012/01/30/how-asynchronous-is-smtpclient-sendasync
Мартин Колл
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