Как я могу отправлять MailMessages, используя несколько потоков?

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

Это не модель производитель-потребитель. Я извлекал, скажем, 10 строк за раз в базу данных. Таблица данных содержит сериализованный объект MailMessage и сведения об отправке SMTP. Если мне нужно использовать, скажем, фиксированное количество потоков (скажем, 6 потоков). Теперь, как мне получить строку из таблицы данных в потоке, а затем отправить почту и снова вернуться, чтобы увидеть, остались ли еще строки?

Подойдет любая простая логика для реализации, желательно с простым примером на C#.

я использую .NET 3.5


person Pankaj Kumar    schedule 24.02.2011    source источник
comment
В чем причина этих нескольких потоков? Извлечение строк или отправка писем?   -  person Daniel Hilgarth    schedule 24.02.2011
comment
Сообщения должны отправляться массово для информационных бюллетеней и приглашений. Существует компонент ASPEmail, который обеспечивает быструю отправку электронной почты с использованием одновременных сеансов. Я хочу попробовать многопоточность, если это поможет повысить производительность.   -  person Pankaj Kumar    schedule 24.02.2011


Ответы (2)


Поскольку отправка электронных писем является процессом, связанным с вводом-выводом, поэтому создание потоков для отправки электронных писем не приведет к значительному ускорению (если вообще будет).

Если вы используете SMTP-сервер, который является частью Windows, то, когда вы «отправляете» электронное письмо, оно фактически не отправляется в этот момент. Он стоит в очереди на сервере, и сервер отправляет их так быстро, как только может. Отправка электронных писем на самом деле является медленным процессом.

Я думаю, что я говорю, что есть два варианта:

  1. Просто отправьте их последовательно и посмотрите, соответствует ли это вашим требованиям к производительности.
  2. Вы можете использовать концепцию параллельного программирования под названием «Параллельные данные». Я привел примеры в сообщении блога Параллельные данные — параллельное программирование на C#/.NET

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

Так что получите свои данные и разделите их на куски или разделы. Все это описано в статье, на которую я указал. Наивной реализацией было бы количество чанков, равное количеству ядер на машине.

Каждый чанк обрабатывается одним потоком. Когда все потоки будут выполнены, все готово. Благодаря новым функциям ThreadPool в .NET 4.0 (если вы используете Parallel.For, PLINQ или Tasks) вы получите некоторые другие преимущества, такие как «краже работы» для дальнейшего ускорения работы.

Думаю, Parallel.For/Parallel.ForEach вам подойдет.

ИЗМЕНИТЬ

Только что заметил требование .NET 3.5. Ну, концепции все еще применимы, но у вас нет Parallel.For/ForEach. Итак, вот реализация (измененная из моего сообщения в блоге), которая использует ThreadPool и использует метод Data Parallel.

    private static void SendEmailsUsingThreadPool(List<Recipient> recipients)
    {
      var coreCount = Environment.ProcessorCount;
      var itemCount = recipients.Count;
      var batchSize = itemCount / coreCount;

      var pending = coreCount;
      using (var mre = new ManualResetEvent(false))
      {
        for (int batchCount = 0; batchCount < coreCount; batchCount++)
        {
          var lower = batchCount * batchSize;
          var upper = (batchCount == coreCount - 1) ? itemCount : lower + batchSize;
          ThreadPool.QueueUserWorkItem(st =>
          {
            for (int i = lower; i < upper; i++)
              SendEmail(recipients[i]);
            if (Interlocked.Decrement(ref pending) == 0)
              mre.Set();
          });
        }
        mre.WaitOne();
      }      
    }

    private static void SendEmail(Recipient recipient)
    {
      //Send your Emails here
    }
  }

  class Recipient
  {
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string EmailAddress { get; set; }
  }

Итак, получите свои данные и вызовите SendEmailUsingThreadPool(), передав ему свои данные. Конечно, не называйте свой метод так :). Если у вас есть DataSet/DataTable, просто измените реализацию, чтобы принять DataSet/DataTable. Этот метод позаботится о разделении ваших данных на куски, поэтому вам не нужно ни о чем беспокоиться. Просто позвоните.

person Shiv Kumar    schedule 24.02.2011
comment
спасибо за подробный ответ. Основная причина внедрения такой системы заключалась в том, что, скажем, 5000 пользователей отправляли почту с помощью SmtpClient, тогда код блокировался на очень долгое время. Поскольку теперь они хранятся в БД, это будет намного быстрее. Потоки нужны для быстрой обработки очереди и передачи ответственности SMTP-серверу. - person Pankaj Kumar; 24.02.2011
comment
+1 за реализацию .Net 3.5. Я попробую это и вернусь к вам. - person Pankaj Kumar; 24.02.2011
comment
@Pankaj, я не уверен, что понял первую часть твоего комментария. К вашему сведению: вы по-прежнему будете использовать SmtpClient для отправки электронных писем. Я предполагаю, что когда вы говорите, что код был заблокирован, вы имеете в виду, что в приложении ASP.NET (основном приложении) код был заблокирован, поэтому вы перемещаете отправку в службу Windows? - person Shiv Kumar; 24.02.2011
comment
@Pankaj, видя, что вы отправляете электронные письма с некоторым интервалом (а не мгновенно), вы хотите, чтобы для этого было обычное консольное приложение, и затем вы можете запускать его в обычное время или в определенный день недели или месяца, используя Планировщик заданий. Его легче отлаживать, и вы всегда можете преобразовать его в службу Windows после нескольких успешных запусков в производственной среде, хотя нет никакой реальной ценности (в данном случае) в том, чтобы запускать его как службу. - person Shiv Kumar; 24.02.2011

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

person gabba    schedule 24.02.2011
comment
точно, но как мне направить их на отдельные потоки? - person Pankaj Kumar; 24.02.2011