You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 08:51:18