Регулирование скорости отправки писем - PullRequest
6 голосов
/ 18 марта 2009

Извините, название немного дерьмовое, я не могу сказать это правильно.

Редактировать: я должен отметить, что это консольное приложение c #

Я создал прототип системы, которая работает следующим образом (это грубый псевдокодиш):

var collection = grabfromdb();

foreach (item in collection) {
    SendAnEmail();
}

SendAnEmail:

SmtpClient mailClient = new SmtpClient;
mailClient.SendCompleted += new SendCompletedEventHandler(SendComplete);
mailClient.SendAsync('the mail message');

SendComplete:

if (anyErrors) {
    errorHandling()
}
else {
    HitDBAndMarkAsSendOK();    
}

Очевидно, что эта установка не идеальна. Если первоначальная коллекция имеет, скажем, 10 000 записей, то она собирается создать до 10 000 экземпляров smtpclient в довольно коротком порядке настолько быстро, насколько это возможно, чтобы пройти по строкам - и, вероятно, произойдет смещение в процессе.

Моя идеальная конечная игра - это одновременная отправка 10 одновременных писем.

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

Я не уверен, что это такая прекрасная идея, и полагаю, что разум Улья мог бы сделать это правильно.

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

<ч />

Обновление

Следуя совету Стивена А. Лоу, у меня теперь есть:

  • Словарь, содержащий мои электронные письма и уникальный ключ (это очередь сообщений
  • Метод FillQue, который заполняет словарь
  • Метод ProcessQue, который является фоновым потоком. Он проверяет очередь и отправляет любые сообщения в очереди SendAsycs.
  • Делегат SendCompleted, который удаляет электронную почту из очереди. И снова вызывает FillQue.

У меня есть несколько проблем с этой настройкой. Я думаю, что я пропустил лодку с фоновой веткой, я должен порождать одну из них для каждого элемента в словаре? Как я могу заставить поток «торчать» из-за отсутствия лучшего слова, если очередь электронной почты очищает конец потока.

<ч />

окончательное обновление

Я поставил 'while (true) {}' в фоновом потоке. Если очередь пуста, она ждет несколько секунд и пытается снова. Если очередь постоянно пуста, я «ломаю» какое-то время, и программа заканчивается ... Работает нормально. Я немного обеспокоен бизнесом while (true), хотя ..

Ответы [ 5 ]

2 голосов
/ 18 марта 2009

Короткий ответ

Использовать очередь в качестве конечного буфера, обработанного собственным потоком.

Длинный ответ

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

Вам понадобятся только блокировки вокруг операций с очередями, и вам нужно будет только (напрямую) управлять одним потоком для обработки очереди. Вы никогда не будете иметь более N + 1 активных потоков одновременно, где N - предел очереди.

1 голос
/ 18 марта 2009

Я бы добавил все свои сообщения в очередь, а затем создал бы 10 потоков, которые отправляли электронные письма, пока очередь не была пуста. Псевдоиш C # (вероятно, не будет компилироваться):

class EmailSender
{
    Queue<Message> messages;
    List<Thread> threads;

    public Send(IEnumerable<Message> messages, int threads)
    {
        this.messages = new Queue<Message>(messages);
        this.threads = new List<Thread>();
        while(threads-- > 0)
            threads.Add(new Thread(SendMessages));

        threads.ForEach(t => t.Start());

        while(threads.Any(t => t.IsAlive))
            Thread.Sleep(50);
    }

    private SendMessages()
    {
        while(true)
        {
            Message m;
            lock(messages)
            {
                try
                {
                    m = messages.Dequeue();
                }
                catch(InvalidOperationException)
                {
                    // No more messages
                    return;
                }
            }

            // Send message in some way. Not in an async way, 
            // since we are already kind of async.

            Thread.Sleep(); // Perhaps take a quick rest
        }
    }
}

Если сообщение одно и то же, и у него много получателей, просто поменяйте сообщение получателем и добавьте один параметр Message в метод Send.

1 голос
/ 18 марта 2009

Я верю, что ваше хакерское решение действительно сработает. Просто убедитесь, что у вас есть оператор блокировки вокруг битов, где вы увеличиваете и уменьшаете счетчик:

class EmailSender
{
  object SimultaneousEmailsLock;
  int SimultaneousEmails;
  public string[] Recipients;

  void SendAll()
  {
    foreach(string Recipient in Recipients)
    {
      while (SimultaneousEmails>10) Thread.Sleep(10);
      SendAnEmail(Recipient);
    }
  }

  void SendAnEmail(string Recipient)
  {
    lock(SimultaneousEmailsLock)
    {
      SimultaneousEmails++;
    }

    ... send it ...
  }

  void FinishedEmailCallback()
  {
    lock(SimultaneousEmailsLock)
    {
      SimultaneousEmails--;
    }

    ... etc ...
  }
}
0 голосов
/ 18 марта 2009

Разве это не то, с чем Thread.Sleep() может справиться?

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

Небольшое вступление о многопоточности можно прочитать здесь (включая Thread.Sleep!) .

Хорошее введение в Windows Services можно прочитать здесь .

0 голосов
/ 18 марта 2009

Вы можете использовать .NET Timer для настройки расписания отправки сообщений. Всякий раз, когда срабатывает таймер, возьмите следующие 10 сообщений, отправьте их все и повторите. Или, если вам нужна общая скорость (10 сообщений в секунду), вы можете включить таймер каждые 100 мс и каждый раз отправлять одно сообщение.

Если вам нужно более расширенное планирование, вы можете посмотреть на структуру планирования, такую ​​как Quartz.NET

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...