发送批量电子邮件时出错 "An asynchronous call is already in progress. It must be completed or canceled before you can call this method"

Getting error while sending bulk email "An asynchronous call is already in progress. It must be completed or canceled before you can call this method"

我创建了一个控制台应用程序,用于将数据从一个数据库 table 迁移到另一个数据库,其中客户记录已迁移,因此我必须通知他们更改密码。

public static async Task<bool> SendRegisterEmail(List<MailMessage> mailMessage)
{
    bool flag = true;
    try
    {
        var smtp = new SmtpClient();
        var taskEmails = mailMessage.Select(x => smtp.SendMailAsync(x));

        await Task.WhenAll(taskEmails); // **Error : An asynchronous call is already in progress. It must be completed or canceled before you can call this method**
    }
    catch (Exception ex)
    {
        throw ex;
    }

    return flag;
}

如果我删除 await Task.WhenAll(taskEmails),电子邮件将成功异步发送,但是当迁移操作完成时,它不会发送所有电子邮件。操作完成后,其中许多在控制台应用程序关闭时仍未发送,我有超过 1,00,000 条记录,因此如何在后台或应用程序中继续电子邮件发送过程 运行 直到所有电子邮件都已成功发送?

这里是数据迁移的代码:

foreach (DataRow SourceReader in DS.Tables[0].Rows)
{
    insertCounter++;
    using (SqlCommand DestinationCommand = DestinationConnection.CreateCommand())
    {
        Console.WriteLine("inserting row...");
        var date = (SourceReader["LockedUntil"] == DBNull.Value ? Convert.ToDateTime("01/01/1753") : Convert.ToDateTime(SourceReader["LockedUntil"]));
        DestinationCommand.CommandText = string.Format(insertQuery1, (SourceReader["CustomerGUID"] == DBNull.Value ? null : SourceReader["CustomerGUID"].ToString()), (SourceReader["FirstName"] == DBNull.Value ? null : SourceReader["FirstName"].ToString()), (SourceReader["LastName"] == DBNull.Value ? null : SourceReader["LastName"].ToString()), (SourceReader["Email"] == DBNull.Value ? null : SourceReader["Email"].ToString()), 1, PasswordManager.Encrypt(SourceReader["FirstName"].ToString() + "1!"), (SourceReader["Phone"] == DBNull.Value ? null : SourceReader["Phone"].ToString()), 1, 0, 0, date.ToString("yyyy-MM-ddTHH:mm:ss"), Convert.ToInt16(SourceReader["BadLoginCount"] == DBNull.Value ? 0 : SourceReader["BadLoginCount"]), Convert.ToInt16(SourceReader["OkToEmail"] == DBNull.Value ? 0 : SourceReader["OkToEmail"]), (SourceReader["CustomerGUID"] == DBNull.Value ? null : SourceReader["CustomerGUID"].ToString()), 2);
        DestinationCommand.ExecuteNonQuery();
        Console.WriteLine("AspNetUser Row inserted...!!! ");
    }
    if ((insertCounter % 100) == emailCounter)
    {
        var message = Email.AddUserForEmail(new User() { Email = (SourceReader["Email"] == DBNull.Value ? null : SourceReader["Email"].ToString()), Password = (SourceReader["FirstName"].ToString() + "1!"), FirstName = (SourceReader["FirstName"] == DBNull.Value ? null : SourceReader["FirstName"].ToString()), LastName = (SourceReader["LastName"] == DBNull.Value ? null : SourceReader["LastName"].ToString()) });
        mailList.Add(message);
        var flag = Email.SendRegisterEmail(mailList);
        emailCounter++;
    }
    else if (insertCounter == totalCount)
    {
        var message = Email.AddUserForEmail(new User() { Email = (SourceReader["Email"] == DBNull.Value ? null : SourceReader["Email"].ToString()), Password = (SourceReader["FirstName"].ToString() + "1!"), FirstName = (SourceReader["FirstName"] == DBNull.Value ? null : SourceReader["FirstName"].ToString()), LastName = (SourceReader["LastName"] == DBNull.Value ? null : SourceReader["LastName"].ToString()) });
        mailList.Add(message);
        var flag = Email.SendRegisterEmail(mailList);
    }
    else
    {
        var message = Email.AddUserForEmail(new User() { Email = (SourceReader["Email"] == DBNull.Value ? null : SourceReader["Email"].ToString()), Password = (SourceReader["FirstName"].ToString() + "1!"), FirstName = (SourceReader["FirstName"] == DBNull.Value ? null : SourceReader["FirstName"].ToString()), LastName = (SourceReader["LastName"] == DBNull.Value ? null : SourceReader["LastName"].ToString()) });
        mailList.Add(message);
    }
    // var i = Email.SendRegisterEmail((SourceReader["Email"] == DBNull.Value ? null : SourceReader["Email"].ToString()), (SourceReader["FirstName"].ToString() + "1!"), (SourceReader["FirstName"] == DBNull.Value ? null : SourceReader["FirstName"].ToString()), (SourceReader["LastName"] == DBNull.Value ? null : SourceReader["LastName"].ToString()));
}

SmtpClient 不允许您同时执行多个异步操作,这就是错误消息告诉您的内容。您正在使用:

var smtp = new SmtpClient();
var taskEmails = mailMessage.Select(x => smtp.SendMailAsync(x));
await Task.WhenAll(taskEmails);

你也没有处理 SmtpClient,这也无济于事。

相反,要么将它们一一发送:

using (var smtp = new SmtpClient()){
    foreach (var email in mailMessage) {
        await smtp.SendMailAsync(email);
    }
}

或者对每个发送使用单独的SmtpClient

Func<MailMessage, Task> sendFunc = async (x) => {
    using (var smtp = new SmtpClient()) {
        await smtp.SendMailAsync(x);
    }
};
var taskEmails = mailMessage.Select(sendFunc);
await Task.WhenAll(taskEmails);

看来您也没有在等待迁移函数发送的那些消息:

// flag is Task<bool> here
var flag = Email.SendRegisterEmail(mailList);

如果这不是拼写错误 - 您需要就地等待它们,或者在某个列表中收集任务并在一个循环后一起等待它们(使用 await Task.WhenAll)。

请注意,如果您要发送大量电子邮件,尤其是发送到同一个域,尤其是并行发送 - 您使用的 SMTP 服务器(或收件人的 SMTP 服务器)可能对此不太满意,可能会将您列入黑名单而

最好使用单独的后台进程,它会发送与您的迁移过程完全无关的电子邮件。只需让迁移过程将有关待处理电子邮件的信息插入到某个持久性存储(数据库 table),然后让另一个应用程序探索该 table 并发送电子邮件。这样迁移过程就不会因电子邮件失败而中断,发送电子邮件的后台进程可以根据需要重试。