Почему SmtpClient.SendAsync может быть вызван только один раз? - PullRequest
22 голосов
/ 23 декабря 2008

Я пытаюсь написать службу уведомлений (для совершенно законных целей, не связанных со спамом) в .NET, используя SmtpClient. Первоначально я просто просматривал каждое сообщение и отправлял его, однако это медленно, и я хотел бы улучшить скорость. Итак, я переключился на использование SendAsync, но теперь получаю следующую ошибку при втором вызове:

An asynchronous call is already in progress. 

Я прочитал это, чтобы означать, что MS наносит вред System.Net.Mail, чтобы предотвратить массовые рассылки. Это правильно? Если это так, есть ли лучший способ сделать это в .NET, и при этом иметь возможность регистрировать результаты каждого электронного письма (что важно для нашего клиента). Если нет, то почему SendAsync может быть вызван только один раз?

Ответы [ 7 ]

35 голосов
/ 23 декабря 2008

Согласно документации :

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

Таким образом, чтобы отправлять несколько писем одновременно, вам нужно несколько экземпляров SmtpClient.

6 голосов
/ 01 сентября 2009

Вы можете использовать следующее:

ThreadPool.QueueUserWorkItem(state => client.Send(msg));

Это должно позволить вашим сообщениям ставиться в очередь и отправляться по мере появления тем.

4 голосов
/ 23 декабря 2008

Я думаю, вы неправильно понимаете класс XXXAsync методов. Цель этих асинхронных вызовов состоит в том, чтобы позволить программе продолжить работу без необходимости метода, чтобы завершить обработку и сначала вернуться Затем вы можете продолжить работу с результатом, подписавшись на что-то вроде XXXReceived события объекта.

Чтобы отправить более одной почты одновременно, вы можете использовать больше Thread с.

4 голосов
/ 23 декабря 2008

Очевидно, что это не попытка остановить массовых рассылок.

Причина в том, что класс SmtpClient не является потокобезопасным. Если вы хотите отправлять несколько электронных писем одновременно, вам нужно создать несколько рабочих потоков (есть несколько способов сделать это в .NET Framework) и создать отдельные экземпляры SmtpClient в каждом из них.

2 голосов
/ 23 декабря 2008

Как заметили все остальные здесь, вы можете отправлять только одно электронное письмо за раз, но способ отправить другое, как только первое было отправлено, - обработать событие .SendCompleted класса SmtpClient, а затем перейти к следующий адрес электронной почты и отправьте его.

Если вы хотите отправить много электронных писем одновременно, то, как сказали другие, используйте несколько объектов SmtpClient.

2 голосов
/ 23 декабря 2008

Вы можете отправлять только по одному для каждого SMTP-клиента. Если вы хотите сделать более одного звонка, создайте более одного клиента SMTP.

НТН,

Колби Африка

1 голос
/ 12 июня 2018

Существует причина для повторного использования SmtpClient , это ограничивает количество подключений к SMTP-серверу. Я не могу создать новый класс SmtpClient класс для каждого потока, на котором строятся отчеты, иначе SMTP-сервер прекратит работу с ошибкой слишком большого числа соединений. Это решение, которое я придумал, когда не смог найти ответ здесь.

В итоге я использовал AutoResetEvent для синхронизации всего. Таким образом, я могу продолжать вызывать мой SendAsync в каждом потоке, но подождать, пока он обработает электронную почту, и использовать событие SendComplete , чтобы сбросить его и продолжить следующее.

Я установил событие автоматического сброса.

        AutoResetEvent _autoResetEvent = new AutoResetEvent(true);

Я настраиваю общий SMTP-клиент, когда создается мой класс.

        _smtpServer = new SmtpClient(_mailServer);
        _smtpServer.Port = Convert.ToInt32(_mailPort);
        _smtpServer.UseDefaultCredentials = false;
        _smtpServer.Credentials = new System.Net.NetworkCredential(_mailUser, _mailPassword);
        _smtpServer.EnableSsl = true;
        _smtpServer.SendCompleted += SmtpServer_SendCompleted;

Затем, когда я вызываю асинхронную отправку, я жду, пока событие не очистится, а затем отправляю следующее.

        _autoResetEvent.WaitOne();
        _smtpServer.SendAsync(mail, mail);
        mailWaiting++;

Я использую событие SMTPClient SendComplete для сброса AutoResetEvent и отправки следующего электронного письма.

private static void SmtpServer_SendCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
        {
            MailMessage thisMesage = (MailMessage) e.UserState;
            if (e.Error != null)
            {
                if (e.Error.InnerException != null)
                {
                    writeMessage("ERROR: Sending Mail: " + thisMesage.Subject + " Msg: "
                                 + e.Error.Message + e.Error.InnerException.Message);
                }

                else
                {
                    writeMessage("ERROR: Sending Mail: " + thisMesage.Subject + " Msg: " + e.Error.Message);
                }
            }
            else
            {
                writeMessage("Success:" + thisMesage.Subject + " sent.");
            }
        if (_messagesPerConnection > 20)
        {  /*Limit # of messages per connection, 
            After send then reset the SmtpClient before next thread release*/
            _smtpServer = new SmtpClient(_mailServer);
            _smtpServer.SendCompleted += SmtpServer_SendCompleted;
            _smtpServer.Port = Convert.ToInt32(_mailPort);
            _smtpServer.UseDefaultCredentials = false;
            _smtpServer.Credentials = new NetworkCredential(_mailUser, _mailPassword);
            _smtpServer.EnableSsl = true;
            _messagesPerConnection = 0;
        }
            _autoResetEvent.Set();//Here is the event reset
            mailWaiting--;
        }
...