限制电子邮件发送过程的速度

时间:2009-03-18 04:34:46

标签: c# multithreading smtpclient

对不起,标题有点蹩脚,我无法正确地说出来。

编辑:我应该注意这是一个控制台c#app

我已经制作了一个像这样工作的系统原型(这是粗略的伪代码):

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个并发电子邮件。

想到一个hacky解决方案:添加一个计数器,当调用SendAnEmail()时递增,并在发送SendComplete时递减。在初始循环中调用SendAnEmail()之前,检查计数器,如果它太高,则在一段时间内休眠,然后再次检查。

我不确定这是一个如此棒的想法,并认为这样的想法会有办法正确地做到这一点。

我对线程知之甚少,不确定它是否适合在这里使用。例如,在后台线程中发送电子邮件,首先检查子线程的数量,以确保没有太多使用。或者如果内置了某种类型的“线程限制”。


更新

根据Steven A. Lowe的建议,我现在有:

  • 包含我的电子邮件和唯一密钥的字典(这是电子邮件队列
  • 填充字典的FillQue方法
  • ProcessQue方法,它是后台线程。它会检查que和SendAsycs中的任何电子邮件。
  • SendCompleted委托,用于从que中删除电子邮件。并再次致电FillQue。

我在使用此设置时遇到了一些问题。我想我已经错过了背景线程的船,我是否应该为词典中的每个项目产生其中一个?如果电子邮件会清空线程结束,我怎么能让线程因为缺少更好的单词而“闲逛”。


最终更新

我在后台线程中添加了'while(true){}'。如果que为空,则等待几秒钟并再次尝试。如果阙重复为空,我'打破'一会儿,程序结束......工作正常。我有点担心“虽然(真实)”的业务但是......

5 个答案:

答案 0 :(得分:2)

简答

使用队列作为有限缓冲区,由其自己的线程处理。

长答案

调用fill-queue方法创建一个电子邮件队列,限于(比如说)10。用前10封未发送的电子邮件填写。启动一个线程来处理队列 - 对于队列中的每个电子邮件,将其发送为异步。当队列空了一会儿睡觉并再次检查。让完成委托从队列中删除已发送或错误的电子邮件并更新数据库,然后调用fill-queue方法将更多未发送的电子邮件读入队列(返回到限制)。

您只需要围绕队列操作进行锁定,并且只需管理(直接)一个线程来处理队列。您永远不会有多于N + 1个线程同时处于活动状态,其中N是队列限制。

答案 1 :(得分:1)

我相信你的hacky解决方案实际上会起作用。只需确保在递增和递减计数器的位周围有一个锁定语句:

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 ...
  }
}

答案 2 :(得分:1)

我会将所有消息添加到队列中,然后生成10个发送电子邮件的线程,直到队列为空。 Pseudo'ish 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
        }
    }
}

如果邮件相同,只有许多收件人,只需将邮件与收件人交换,然后向Send方法添加一个Message参数。

答案 3 :(得分:0)

您可以使用.NET Timer设置发送消息的计划。每当计时器触发时,抓住接下来的10条消息并将它们全部发送,然后重复。或者,如果您想要一般(每秒10条消息)速率,您可以让计时器每100毫秒触发一次,并且每次都发送一条消息。

如果您需要更高级的计划,可以查看Quartz.NET

等计划框架

答案 4 :(得分:0)

这不是Thread.Sleep()可以处理的东西吗?

你认为背景线程在这里可以起到很好的作用是正确的。基本上你想要做的是为这个进程创建一个后台线程,让它以自己的方式运行,延迟和所有,然后在进程完成时终止线程,或者无限期地保持它(将其转换为Windows服务或类似的东西将是一个好主意。)

一点点intro on multi-threading can be read here (with Thread.Sleep included!)

一个不错的intro on Windows Services can be read here