代码之家  ›  专栏  ›  技术社区  ›  James

使用QueueUserWorkItem在单独的线程上发送电子邮件

  •  6
  • James  · 技术社区  · 15 年前

    我有一个控制台应用程序,它向不同的收件人发送定制的电子邮件(带附件),我想同时发送它们。我需要创建单独的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);
    

    似乎起作用了。我的想法是,无论出于什么原因,有些电子邮件仍然没有发送(即使在 发送 方法已运行)。给这些额外的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();
    }
    
    4 回复  |  直到 10 年前
        1
  •  4
  •   Aaronaught    15 年前

    让我对你的代码感到困扰的一件事是你调用 events.Add 在线程方法中。这个 Dictionary<TKey, TValue> 类不是线程安全的;此代码不应位于线程内部。

    更新: 我认为超斯潘发布了一个很好的实现,但我会使它更简单,使它成为没有任何东西可以做到的。 可能地 在线程安全方面出错:

    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 实例(可能是单实例或静态声明的东西)。

        2
  •  2
  •   Will    15 年前

    你可能想这么做…

    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());
    
        3
  •  2
  •   Will    15 年前

    它不起作用的原因是当他点击events.values.toarray()时 并非所有排队的委托都已执行 因此 并非所有autoreseteevent实例都已添加到字典中 .

    在Values属性上调用ToArray()时,只会得到已经添加的实例!

    这意味着在被阻塞的线程继续之前,您将只等待一些电子邮件被同步发送。其余的电子邮件还没有被线程池线程处理。

    有更好的方法,但是 这是一个黑客 当您最终想要阻塞调用线程时,异步地做一些事情似乎没有意义…

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

    好的,考虑到以下要求:

    1. 控制台应用程序
    2. 大量电子邮件
    3. 尽快发送

    我将创建以下应用程序:

    从文本文件(file.readalllines)加载电子邮件。接下来,创建2个*(个CPU核心)线程。确定每个线程要处理的行数;即,将行数(每行相加)除以线程数,四舍五入。接下来,设置每个线程遍历其地址列表的任务(使用skip(int).take(int)来划分行)并同步发送()每个电子邮件。每个线程将创建并使用自己的smtpclient。当每个线程完成时,它会增加存储在共享位置中的int。当该int等于线程数时,我知道所有线程都已完成。在再次检查之前,主控制台线程将在设置的时间长度内连续检查此数字是否相等和sleep()。

    这听起来有点笨拙,但会奏效的。您可以调整线程的数量以获得单个计算机的最佳吞吐量,然后从中进行推断以确定适当的线程数量。在完成之前,肯定有更优雅的方法来阻塞控制台线程,但没有一个简单的方法。

        4
  •  0
  •   robor    10 年前

    我也遇到了类似的问题(使用线程中的smtpclient,电子邮件只是间歇性地到达)。

    最初,发送电子邮件的类创建了一个smtpclient实例。这个问题通过每次需要发送电子邮件时更改代码来创建smtpclient的新实例来解决。 处理smtpclient(使用using语句)。

     SmtpClient smtpClient = InitSMTPClient();
     using (smtpClient)
     {
        MailMessage mail = new MailMessage();
        ...
        smtpClient.Send(mail);
     }