【问题标题】: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"发送批量电子邮件时出现错误“异步调用已在进行中。必须先完成或取消,然后才能调用此方法”
【发布时间】:2018-04-26 07:05:55
【问题描述】:

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

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()));
}

【问题讨论】:

    标签: c# asynchronous console-application smtpclient


    【解决方案1】:

    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 服务器)可能对此不太满意,可能会将您列入黑名单一段时间。

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

    【讨论】:

    • 感谢您的回答。我试过你上面提到的代码。我正在向 100 人一组的用户发送电子邮件,但仅发送 100 封电子邮件需要很长时间,而且我有超过 1,00,000 条记录要迁移并通过电子邮件通知他们,因此需要花费大量时间,有时还会中断介于两者之间,因此迁移过程中断。那么有没有其他方法可以在不中断迁移过程的情况下在后台发送电子邮件?
    • 每次发送 90 到 95 封邮件后,每次都会中断。错误:服务不可用,正在关闭传输通道。服务器响应为:4.7.0 稍后重试,关闭连接
    • @ErVatsalDPatel 好吧,这只是意味着您发送电子邮件的远程 smtp 服务器对这种负载不满意。我已经更新了一些答案,但重点是 - 使用完全独立的流程(应用程序)来发送电子邮件。迁移过程不应该关心这个。
    猜你喜欢
    • 1970-01-01
    • 2015-11-25
    • 2010-12-11
    • 1970-01-01
    • 1970-01-01
    • 2012-04-07
    • 1970-01-01
    • 2018-02-02
    • 2016-03-25
    相关资源
    最近更新 更多