Отправка электронных писем на отдельном использовании потоков QueueUserWorkItem

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

Отрывок

var events = new Dictionary<Guid, AutoResetEvent>();
foreach (...)
{
    ThreadPool.QueueUserWorkItem(delegate
    {
        var id = Guid.NewGuid();
        events.Add(id, new AutoResetEvent(false));
        var alert = // create custom class which internally creates SmtpClient & Mail Message
        alert.Send();
        events[id].Set();
    });   
}
// wait for all emails to signal
WaitHandle.WaitAll(events.Values.ToArray());

Я заметил (периодически), что иногда не все электронные письма прибывают в определенные почтовые ящики с вышеупомянутым кодом. Я думал бы то использование Send SendAsync означал бы, что электронная почта определенно отправила из приложения. Однако добавляя следующую строку кода после WaitHandle.WaitAll строка:

System.Threading.Thread.Sleep(5000);

Кажется, работает. Мои взгляды по любой причине, некоторые электронные письма все еще не были посланы (даже после Send метод работал). Предоставление тех дополнительных 5 секунд, кажется, дает приложению достаточно времени для окончания.

Это - возможно, проблема со способом, которым я ожидаю на электронных письмах для отправки? Или действительно ли это - проблема с фактическим, Отправляют метод? Электронное письмо было определенно послано из приложения, после того как мы передаем эту строку?

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

Обновление

Согласно просьбе вот код SMTP:

SmtpClient client = new SmtpClient("Host");
FieldInfo transport = client.GetType().GetField("transport", BindingFlags.NonPublic | BindingFlags.Instance);
FieldInfo authModules = transport.GetValue(client).GetType()
    .GetField("authenticationModules", BindingFlags.NonPublic | BindingFlags.Instance);
Array modulesArray = authModules.GetValue(transport.GetValue(client)) as Array;
modulesArray.SetValue(modulesArray.GetValue(2), 0);
modulesArray.SetValue(modulesArray.GetValue(2), 1);
modulesArray.SetValue(modulesArray.GetValue(2), 3);
try
{
    // create mail message
    ...
    emailClient.Send(emailAlert);
}
catch (Exception ex)
{
    // log exception
}
finally
{
    emailAlert.Dispose();
}
6
задан AnFi 26 March 2013 в 13:49
поделиться

3 ответа

Одна из вещей, которая меня беспокоит в вашем коде, это то, что вы вызываете events.Add в потоковом методе. Класс Dictionary не является потокобезопасным; этот код не должен находиться внутри потока.

Обновление: Я думаю, что ChaosPandion разместил хорошую реализацию, но я бы сделал ее еще проще, чтобы ничто не могло возможно пойти не так с точки зрения потокобезопасности:

var events = new List<AutoResetEvent>();
foreach (...)
{
    var evt = new AutoResetEvent();
    events.Add(evt);
    var alert = CreateAlert(...);
    ThreadPool.QueueUserWorkItem(delegate
    {           
        alert.Send();
        evt.Set();
    });
}
// wait for all emails to signal
WaitHandle.WaitAll(events.ToArray());

Здесь я полностью удалил словарь, и все экземпляры AutoResetEvent создаются в том же потоке, который позже выполняет WaitAll. Если этот код не работает, то это должно быть проблемой с самим сообщением; либо сервер сбрасывает сообщения (сколько вы посылаете?), либо вы пытаетесь поделиться чем-то нечитаемым между экземплярами Alert (возможно, одиночной кнопкой или чем-то объявленным статически).

4
ответ дан 16 December 2019 в 21:40
поделиться
[

]Причина, по которой он не работает, заключается в том, что когда он попадает в events.Values.ToArray() []не все делегаты в очереди выполнили [] и поэтому []не все экземпляры AutoResetEvent были добавлены в словарь[]. [

] [

]Когда вы вызываете ToArray() в свойстве Values, вы получаете только те экземпляры ARE, которые уже добавлены![

] [

]Это означает, что вы будете ждать только несколько писем, которые будут отправлены синхронно, прежде чем заблокированный поток продолжится. Остальные письма еще не обработаны потоками ThreadPool.[

] [

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

] [
var doneLol = new AutoResetEvent();

ThreadPool.QueueUserWorkItem(
delegate
{
  foreach (...)
  {
    var id = Guid.NewGuid();
    var alert = HurrDurr.CreateAlert(...);
    alert.Send();
  }
  doneLol.Set();
});   

doneLol.WaitOne();
] [
] [

]Хорошо, учитывая следующие требования:[

] [
    ] [
  1. ]Console App[
  2. ] [
  3. ]Lots of emails[
  4. ] [
  5. ]Sent as fast as possible[
  6. ] [
] [

]I'd create the following application:[

] [

]Load the e-mails from a text file (File.ReadAllLines). Далее, создадим 2*(# ядер процессора) Потоки. Определите количество обрабатываемых строк на поток, т.е. разделите количество строк (addy на строку) на количество потоков, округляя их. Далее, установите для каждого потока задачу просмотра списка адресов (используйте Skip(int).Take(int) для разделения строк) и Send()ing каждого письма синхронно. Каждый поток будет создавать и использовать свой собственный SmtpClient. По мере того, как каждый поток будет дополнять друг друга, он увеличивает int, хранящуюся в общем хранилище. Когда эта int равна количеству потоков, я знаю, что все потоки завершены. Главный поток консоли будет постоянно проверять это число на равенство и Sleep() в течение заданного времени, прежде чем проверять его снова.[

] [

]Это звучит немного kludgy, но это будет работать. Вы можете настроить количество потоков, чтобы получить наилучшую пропускную способность для отдельной машины, а затем экстраполировать из этого, чтобы определить правильное количество потоков. Есть определенно более элегантные способы заблокировать консольную нить до конца, но ни один из них не так прост. [

]
2
ответ дан 16 December 2019 в 21:40
поделиться

Вероятно, вы захотите это сделать....

var events = new Dictionary<Guid, AutoResetEvent>();
foreach (...)
{
    var id = Guid.NewGuid();
    events.Add(id, new AutoResetEvent(false));
    ThreadPool.QueueUserWorkItem((state) =>
    {           
        // Send Email
        events[(Guid)state].Set();
    }, id);   
}
// wait for all emails to signal
WaitHandle.WaitAll(events.Values.ToArray());
2
ответ дан 16 December 2019 в 21:40
поделиться
Другие вопросы по тегам:

Похожие вопросы: