我正在开发一个发送大量电子邮件的多线程异步表单应用程序。 我几乎完成了应用程序,但对性能有所顾虑。如果你用更好的最佳实践启发我,它将是完美的,我将真正学习基础知识,因为我是一名初级开发人员。
以下是问题: 首先,我有一个触发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的方式对你来说是否合乎逻辑? 我怎样才能以最佳方式测试性能并优化线程池的工作? 你有什么建议和教导以上任何一个?
非常感谢
答案 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是线程安全的,因此您可以使用它安全地添加和删除多个线程中的项目。