【问题标题】:Thread Pool Optimization线程池优化
【发布时间】:2011-03-10 18:01:39
【问题描述】:

我正在开发一个发送大量电子邮件的多线程异步表单应用程序。 我几乎完成了应用程序,但对性能感到担忧。如果您用更好的最佳实践启发我,那将是完美的,我将真正学习基础知识,因为我是一名初级开发人员。

这是一个问题: 首先,我有触发 Windows.Forms.Timer 组件的应用程序的“开始”按钮。

private void btnStart_MouseClick(object sender, MouseEventArgs e)
{
    timerNewCampaignChecker.Tick += new EventHandler(timerNewCampaignChecker_Tick);
    timerNewCampaignChecker.Enabled = true;
    timerNewCampaignChecker.Start();
}

应用程序需要每 3 秒检查一次数据库是否有新的广告系列(第一种方法),自定义广告系列详细信息(第 2 种方法)内嵌到用户输入,并通过电子邮件将广告系列发送给接收者(第三种方法)。 为了每 3 秒检查一次数据库中的新广告系列,我有上述计时器(间隔为 3 秒),它通过检查新广告系列来启动流程:

MethodInvoker invoker;
private void timerNewCampaignChecker_Tick(object sender, EventArgs e)
{
    invoker = new MethodInvoker(CheckNewCampaigns);
    invoker.BeginInvoke(null, null);
}

因此,计时器每 3 秒触发一次 CheckNewCampaigns 方法,让流程从第一步开始。 从计时器本身启动流程和线程池结构是否正确??? 现在 CheckNewCampaigns 检查 DB,创建一个 List 对象,并且对于每个活动,它将对象的详细信息发送到另一个方法通过调用 ThreadPool。

delegate bool StepCaller(int campaignID);
private void CheckNewCampaigns()
{   
    StringBuilder builder = new StringBuilder();
    StepCaller stepCaller = new StepCaller(PrepareCampaignEmail);
    IEnumerable<Campaigns> CampaignsList = DatabaseManager.GetCampaignsList(DatabaseManager.GetNewCampaigns());

    foreach (Campaigns Campaign in CampaignsList)
    {
        stepCaller.BeginInvoke(Campaign.CampaignID, new AsyncCallback(PrepareEmailCallback), null); 
    }
}

我必须在第一种方法中编写 endInvoke 代码,还是应该直接触发并忘记??。因为最重要的是应用程序有一个流程,参数在方法之间传递,第二个方法应该知道第一个何时完成,以便它可以获取活动ID并自定义它。但是在执行这些操作时,应该启动一个新流程并检查第一种方法的新活动。并且电子邮件也应该同时发送。 新方法(第二个)自定义活动并通过使用 ThreadPool 调用另一个方法(第三个)。 这里是:

private bool PrepareCampaignEmail(int campaignID)
{
    EmailCaller emailCaller = new EmailCaller(SendEmail);
    IEnumerable<Subscribers> SubscribersList = DatabaseManager.GetCampaignSubscribersList(DatabaseManager.GetCampaignSubscribers(campaignID)); 
    CampaignCustomazition campaignCustomizer;
    List<CampaignCustomazition> campaignCustomizedList = new List<CampaignCustomazition>();  // foreach in içindeki beginInvoke ı dışarıya çıkardığımda kullanılacak
    foreach (Subscribers subscriber in SubscribersList)
    {
        campaignCustomizer = new CampaignCustomazition(campaignID);
        campaignCustomizer.CustomizeSource(campaignID, out campaignCustomizer.source, out campaignCustomizer.format);
        campaignCustomizer.CustomizeCampaignDetails(campaignID, out campaignCustomizer.subject, out campaignCustomizer.fromName, out campaignCustomizer.fromEmail, out campaignCustomizer.replyEmail);
        campaignCustomizer.CustomizeSubscriberDetails(campaignID, out campaignCustomizer.subscriberID, out campaignCustomizer.email, out campaignCustomizer.fullName);
        IAsyncResult result = emailCaller.BeginInvoke(campaignCustomizer, null, null);
        try
        {
            emailCaller.EndInvoke(result);
        }
        catch(Exception ex)
        {
            Trace.WriteLine(ex.Message);
        }
    }
    return false;
}

我猜这第二步有一个主要问题。数据库操作将近5-6次。这可能是线程的问题。它似乎工作正常,但我的方式可能是错误的。一天可以有几十个活动。这是一个问题,还是我的方式好吗?(顺便说一下,数据库操作没有那么苛刻和复杂)。

准备好邮件后,就可以发送上面调用的邮件了。第三种方法是一个简单的 SMTP 方法,它发送电子邮件并记录它。

最后一个方法也没有 EndInvoke 功能,但这是应用程序的最后一个方法,作为刚刚发送的邮件。

我处理定时器和线程池的方式在你看来合乎逻辑吗? 如何以最佳方式测试性能并优化线程池的工作? 对于上述任何一项,您有什么建议和教导?

非常感谢。

【问题讨论】:

  • 嗯,不回答这个问题似乎会提高我收到更少垃圾邮件的几率。这很有吸引力。
  • 一篇文章有​​很多问题...
  • 粗略阅读后,我发现:一个 3 秒的计时器可以处理“一天数十个活动”。这似乎有点矫枉过正。尽管帖子很长,但我们不知道我们正在谈论多少电子邮件/小时,如何处理失败,......以及这是多么合法/垃圾邮件?运动一词表明汉斯可能有一个观点。
  • OP 的昵称解释了问题中使用的文本数量 :)
  • @Hans 发送大量电子邮件有很多正当理由,我严重怀疑垃圾邮件发送者是否需要咨询 StackOverflow 以获取有关如何有效发送电子邮件的帮助。

标签: c# multithreading timer threadpool


【解决方案1】:

首先,您应该知道 .NET 有多种计时器选项可供选择,具体取决于您的需要。当您希望回调在 UI 线程上执行时使用 System.Windows.Forms.Timer(因为您需要以任何方式刷新 UI)。 System.Timers.Timer 和 System.Threading.Timer 都在后台线程上执行回调。您可以在MSDN article 中了解更多相关信息。

在使用 BeginInvoke 异步执行委托时,应始终调用 EndInvoke,如 documentation 中所述。要以即发即弃的方式执行方法,推荐的方法是使用 ThreadPool.QueueUserWorkItem(.NET 4 之前)或 Task.TaskFactory.StartNew (.NET 4)。后者的优点是线程和任务之间没有 1:1 的相关性 - 调度程序将选择“适当”数量的线程来使用并为您安排在这些线程上执行的任务。

您不可能仅仅通过生成许多任务来获得最佳性能。我将基于新的 ConcurrentQueue 类(在 .NET 4 中添加)创建一个自定义队列类,并将传出的电子邮件放入其中,然后通过将邮件传递到 SMTP 服务器来生成一些任务来清空队列。 ConcurrentQueue 是线程安全的,因此您可以使用它在多个线程中安全地添加和删除项目。

【讨论】:

  • 非常感谢 Morten,我会尽我所能写出更好的代码。
猜你喜欢
  • 1970-01-01
  • 2022-01-10
  • 2011-06-01
  • 1970-01-01
  • 2011-06-11
  • 2018-09-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多