C#开发求助:ListBox添加项后定时发送邮件功能异常排查
问题分析与解决方案
先帮你梳理下当前代码里的几个核心问题,这些是导致功能无法正常运行的关键:
1. Timer Tick事件逻辑严重错误
- 每次Tick都调用
statmessage(),会反复清空ListBox并重新加载数据,导致之前的项全部丢失,根本无法追踪每个项的显示时长。 - 变量
i的判断逻辑完全颠倒:if (i < 1)才执行内部代码,但i = listBox1.SelectedItems.Count + 1,初始情况下SelectedItems.Count是0,i等于1,这会直接跳过所有核心逻辑;就算进入逻辑,i=0时访问listBox1.Items[i]会触发索引越界异常。 EItem类缺少Added属性的定义与赋值:你在代码里用了exp.Added,但statmessage()里创建EItem时只传入了字符串,没有记录项的添加时间,这会直接抛出编译或运行时错误。- 全局
counter变量的逻辑完全不符合需求:你需要的是对每个ListBox项单独计时,而不是用一个全局计数器控制发送次数。
2. 邮件发送缺少必要数据
从数据库查询的email和bemail(收件人邮箱)没有存储到EItem中,SendMessage()方法根本拿不到收件人信息,无法完成邮件发送。
修复后的完整解决方案
第一步:修正EItem类,存储必要数据
首先定义完整的EItem类,包含业务数据和计时所需的添加时间:
public class EItem { // 业务数据 public DateTime OrderDate { get; set; } public string TransactionNo { get; set; } public string RecipientEmail { get; set; } public string BackupEmail { get; set; } // 计时用:项添加到ListBox的时间 public DateTime AddedTime { get; set; } // 用于ListBox显示的文本 public override string ToString() { return $"{OrderDate} - Transaction No.: {TransactionNo} has been submitted successfully all the details at {RecipientEmail}."; } }
第二步:重构statmessage()方法
只在初始化或需要手动刷新时调用,不再让Timer反复触发,同时正确填充EItem的所有属性:
public void statmessage() { string connstr = "server = ***.***.***.***; database = dbname; UID = ****; password = ****;"; using (SqlConnection dbconn = new SqlConnection(connstr)) { string sqlstr = @"select a.or_date, a.TransactionNo , a.email, a.bemail from vw_forEmailSending a where a.or_date between '2019-01-01 00:00:00.000' and '2500-12-31 00:00:00.000' order by or_date asc"; SqlCommand cmd = new SqlCommand(sqlstr, dbconn); dbconn.Open(); using (SqlDataReader rdr = cmd.ExecuteReader()) { listBox1.BeginUpdate(); listBox1.Items.Clear(); while (rdr.Read()) { var item = new EItem { OrderDate = rdr.GetSqlDateTime(0).Value, TransactionNo = rdr.GetString(1), RecipientEmail = rdr.GetString(2), BackupEmail = rdr.IsDBNull(3) ? null : rdr.GetString(3), AddedTime = DateTime.Now // 记录当前添加时间 }; listBox1.Items.Add(item); } lblcount.Text = listBox1.Items.Count.ToString(); listBox1.EndUpdate(); listBox1.Refresh(); } } }
注意:这里用了
using语句自动释放数据库连接和DataReader,避免资源泄漏。
第三步:重构Timer Tick事件逻辑
现在Timer的职责是遍历所有未处理的项,检查是否达到发送时间,发送后标记或移除项:
// 新增一个集合记录已发送的交易号,避免重复发送 private HashSet<string> sentTransactions = new HashSet<string>(); private int sendDelaySeconds = 5; // 指定的发送延迟秒数 private void timer1_Tick(object sender, EventArgs e) { // 遍历ListBox的项,倒序遍历避免移除项时索引混乱 for (int i = listBox1.Items.Count - 1; i >= 0; i--) { var item = listBox1.Items[i] as EItem; if (item == null || sentTransactions.Contains(item.TransactionNo)) continue; // 检查是否超过指定延迟时间 var timeElapsed = DateTime.Now - item.AddedTime; if (timeElapsed.TotalSeconds >= sendDelaySeconds) { // 异步发送邮件,避免卡住UI Task.Run(() => SendMessage(item)); // 标记为已发送 sentTransactions.Add(item.TransactionNo); // 可选:从ListBox移除已处理的项 listBox1.Items.RemoveAt(i); lblcount.Text = listBox1.Items.Count.ToString(); } } // 可选:当所有项都处理完后,可以停止Timer if (listBox1.Items.Count == 0) timer1.Stop(); }
第四步:完善SendMessage()方法
接收EItem参数,获取收件人信息并发送邮件:
private void SendMessage(EItem item) { try { // 这里替换成你的邮件发送逻辑,比如使用SmtpClient或MailKit // 示例逻辑: var mailMessage = new System.Net.Mail.MailMessage(); mailMessage.From = new System.Net.Mail.MailAddress("your-sender-email@example.com"); mailMessage.To.Add(item.RecipientEmail); if (!string.IsNullOrEmpty(item.BackupEmail)) mailMessage.CC.Add(item.BackupEmail); mailMessage.Subject = $"Transaction {item.TransactionNo} Notification"; mailMessage.Body = $"Your transaction {item.TransactionNo} submitted at {item.OrderDate} has been processed."; using (var smtpClient = new System.Net.Mail.SmtpClient("your-smtp-server", 587)) { smtpClient.Credentials = new System.Net.NetworkCredential("smtp-username", "smtp-password"); smtpClient.EnableSsl = true; smtpClient.Send(mailMessage); } // 可选:在UI线程更新状态(需要Invoke) this.Invoke((Action)(() => { listBox1.Items.Add($"Mail sent for Transaction {item.TransactionNo}"); lblcount.Text = listBox1.Items.Count.ToString(); })); } catch (Exception ex) { // 异常处理,记录日志或提示用户 this.Invoke((Action)(() => { listBox1.Items.Add($"Failed to send mail for {item.TransactionNo}: {ex.Message}"); lblcount.Text = listBox1.Items.Count.ToString(); })); } }
第五步:初始化逻辑调整
在窗体构造函数或Load事件中初始化Timer和加载数据:
public Form1() { InitializeComponent(); // 设置Timer间隔为1秒(根据需求调整) timer1.Interval = 1000; // 加载初始数据 statmessage(); // 启动Timer timer1.Start(); }
额外建议
- 数据库连接字符串建议放在配置文件(App.config)中,不要硬编码。
- 邮件发送推荐使用更现代的库如MailKit替代
SmtpClient(SmtpClient已被标记为过时)。 - 可以添加日志记录,方便排查发送失败的问题。
内容的提问来源于stack exchange,提问作者LM Ochoa
相关产品推荐
相关产品推荐

