发送批量电子邮件时出错"异步调用已在进行中.必须先完成或取消,然后才能调用此方法"

Er *_*tel 3 c# asynchronous console-application smtpclient

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

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;
}
Run Code Online (Sandbox Code Playgroud)

如果我删除了等待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()));
}
Run Code Online (Sandbox Code Playgroud)

Evk*_*Evk 14

SmtpClient不允许您同时执行多个异步操作,这是错误消息告诉您的.你是这样做的:

var smtp = new SmtpClient();
var taskEmails = mailMessage.Select(x => smtp.SendMailAsync(x));
await Task.WhenAll(taskEmails);
Run Code Online (Sandbox Code Playgroud)

你也没有处置SmtpClient,这也无济于事.

相反,要么逐个发送它们:

using (var smtp = new SmtpClient()){
    foreach (var email in mailMessage) {
        await smtp.SendMailAsync(email);
    }
}
Run Code Online (Sandbox Code Playgroud)

或者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);
Run Code Online (Sandbox Code Playgroud)

您似乎也没有等待来自迁移功能的那些发送:

// flag is Task<bool> here
var flag = Email.SendRegisterEmail(mailList);
Run Code Online (Sandbox Code Playgroud)

如果这不是拼写错误 - 您需要等待它们到位,或者在某个列表中收集任务并在循环之后等待它们(with await Task.WhenAll).

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

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