线程池优化

时间:2011-03-10 18:01:39

标签: c# multithreading timer threadpool

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

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

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

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

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

因此,计时器每3秒触发一次CheckNewCampaigns方法,让进程从第1步开始。 从计时器本身启动flow和ThreadPool结构是否正确??? 现在CheckNewCampaigns检查数据库,创建一个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代码,或者我应该解雇并忘记?。因为最重要的是应用程序有一个流,参数在方法之间传递,第二个方法应该知道第一个方法何时完成,以便它可以获取campaignID并自定义它。但在执行这些操作时,应启动新流程并检查新的广告系列,这是第一种方法。此外,电子邮件也应该在同一时间发送。 新方法(第二个)自定义广告系列并使用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次。这可能是线程的问题。它似乎工作正常,但我的方式可能是错的。有一天可能会有数十场运动。这是一个问题,还是我的方式很好?(顺便说一句DB操作并不那么苛刻和复杂。)

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

这最后一个方法也没有EndInvoke函数,但这是应用程序的最后一个方法,就像刚发送的邮件一样。

我处理计时器和ThreadPool的方式对你来说是否合乎逻辑? 我怎样才能以最佳方式测试性能并优化线程池的工作? 你有什么建议和教导以上任何一个?

非常感谢

1 个答案:

答案 0 :(得分:2)

首先,您应该知道.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是线程安全的,因此您可以使用它安全地添加和删除多个线程中的项目。