【问题标题】:Fire and forget with async使用异步触发并忘记
【发布时间】:2014-12-06 07:40:04
【问题描述】:

考虑这段代码:

public async Task<Status> SendMessage(Message message)
{
    List<IMessage> _messageDispatchers = new List<IMessage>();
    try
    {
        Object[] args = new Object[] { _message };
        IMessage endpoint = (IMessage)Activator.CreateInstance(Type.GetType(_message.AgentDLLName), args);
        _messageDispatchers.Add(endpoint);

        foreach (IMessage dispatcher in _messageDispatchers)
        {
            await Task.Run(() => dispatcher.SendMessage(_message));
        }
        return await Task.Run(() => Status.Success);
    }
    catch (Exception ex)
    {
        logger.Log(LoggerLevel.Error, ex.Message);
        return Status.EmailSendingFailed;
    }

}

发送消息:

public async Task<Status> SendMessage(OutboundMessage outboundmessage)
{
    string strMessage = string.Empty;
    string subject = string.Empty;
    MessageServices objService = new MessageServices();
    try
    {
        var config = (from SmtpConfigurationElement ms in AppConfiguration.Instance.Smtps
                      where ms.Key == "smtp"
                      select ms).Single();

        SmtpClient smtpClient = new SmtpClient(config.Host);
        smtpClient.Port = Convert.ToInt32(config.port);
        smtpClient.EnableSsl = true;
        smtpClient.Credentials = new NetworkCredential(config.UserName, config.Password);

        string[] strToList = outboundmessage.ToList.Split(';');
        MailMessage mail = new MailMessage();
        mail.From = new MailAddress(outboundmessage.FromAddress);

        if (strToList.Length > 0)
        {
            for (int j = 0; j < strToList.Length; j++)
            {
                mail.To.Add(strToList[j]);
            }
        }
        else
        {
            _LOGGER.Log(LoggerLevel.Information, "SMTP Mail Send failed as ToList is not correct");
            return Status.Failed;
        }

        if (!string.IsNullOrEmpty(outboundmessage.CCList))
        {
            string[] strCCList = outboundmessage.CCList.Split(';');
            if (strCCList.Length > 0)
            {
                for (int k = 0; k < strCCList.Length; k++)
                {
                    mail.CC.Add(strToList[k]);
                }
            }
        }

        if (!string.IsNullOrEmpty(outboundmessage.Attachments))
        {
            System.Net.Mail.Attachment attachment;
            attachment = new System.Net.Mail.Attachment(outboundmessage.Attachments);
            mail.Attachments.Add(attachment);
        }

        strMessage = await objService.ReplaceMessageWithPlaceholders(outboundmessage.PlaceholderValues, outboundmessage.MessageBody);
        subject = await objService.ReplaceMessageWithPlaceholders(outboundmessage.PlaceholderValues, outboundmessage.Subject);
        mail.Body = strMessage;
        mail.Subject = subject;
        mail.IsBodyHtml = true;
        await Task.Run(() => smtpClient.Send(mail));


        return Status.Success;
    }
    catch (Exception ex)
    {
        return Status.Failed;
    }
}

以及对 SendMessage 的调用:

public Status MarketingEmail(OutboundMessage _message)
{
    try
    {
        _message.MessageCreatedDate = System.DateTime.Now;
        processor.SendMessage(_message);
        return Status.Success;
    }
    catch (Exception ex)
    {
        _LOGGER.Log(LoggerLevel.Error, "Error in Marketing Email" + ex.ToString());
        return Status.InsertFailed;
    }
}

整个想法是创建一个工作流程,其中发送电子邮件是最后一项任务,这应该是一劳永逸的事情。

现在对 processor.SendMessage(_message) 的调用有这样的建议:

因为没有等待这个调用,所以执行当前方法 在呼叫完成之前继续。考虑应用“等待” 调用结果的运算符。

这是一个有效的东西,因为 async 和 await 需要一起使用。

问题:

  1. 如果忽略该建议,当前的方法是否会正常工作? (我之所以问这个问题,是因为它仍处于开发阶段,我现在可以进行建议的设计更改,而不是以后面对任何关键问题。)
  2. 考虑到上述要求,在设计工作流时建议的最佳做法是什么?

【问题讨论】:

  • Status.Success 是枚举吗?如果是,你不应该做Task.Run(() =&gt; Status.Success)return Task.FromResult(Status.Success) 或只是return Status.Success; 适当的,不需要启动一个任务并等待它。

标签: c# asynchronous


【解决方案1】:

当前的方法将“有效”,因为它将继续到 return Status.Success;,而无需等待对 processor.SendMessage(_message); 的调用完成。

但是,由于该调用已被触发并被遗忘,并且 SendMessage 过载不会在 catch 块中进行任何登录,因此您将面临电子邮件发送失败但没有人收到通知的风险。

异步电子邮件发送的常用方法是:将电子邮件存放在其他地方(通常是消息队列或数据库),然后设置一个单独的异步进程来读取排队的电子邮件并发送它们。如果成功,它会将电子邮件标记为已发送。如果失败,它会再次尝试(直到某个时间限制或重试次数),然后如果它放弃,它可以触发通知或设置一个以后可以检查的标志。

然后您的代码基本上会说“好的,电子邮件已成功排队”,而不是“好的,电子邮件已发送”。将实际发送移至单独的进程更加可靠。

【讨论】:

  • P.S.如果您要实现电子邮件队列方法,您可能很想将MailMessage 序列化为 JSON 并以这种方式保存。但它不会反序列化回MailMessage 实例,因为MailAddress 类(MailMessage 在内部使用)没有无参数构造函数。有一些方法可以解决这个问题,但我们发现将电子邮件保存到数据库表中更容易,该表中包含主题、发件人、收件人、抄送、密件抄送、正文、日期创建、日期发送等的离散列。希望这会有所帮助。
  • 这个答案是正确的,但在说“但没有人收到通知”时不清楚。技术原因是,如果没有await,.NET CLR 不会将异常(如果有)传递回调用者线程,并且异常会静默下降。因此,C# 编译器会生成一个警告来明确警告开发人员。
猜你喜欢
  • 2020-04-25
  • 1970-01-01
  • 2010-09-14
  • 1970-01-01
  • 2016-06-21
  • 1970-01-01
  • 2013-09-01
  • 2017-11-30
  • 1970-01-01
相关资源
最近更新 更多