发送电子邮件后代码挂起,但电子邮件发送正常(静态异步任务)

Code hangs after sending email but email is sent OK (static async Task)

我正在编写一些 C# 代码来发送电子邮件(通过 Mailjet/Azure)。它确实发送了电子邮件,但由于某种原因,在单步执行代码时我从未通过这行代码....

MailjetResponse response = await client.PostAsync(request);

它只是在那个时候挂起。知道为什么吗?同样,邮件发送成功!

  public static async Task<bool> SendEmailWithAttachment(string toAddress, string subject, string messageBody, bool sendBCCYesNo, bool sendFromInfoAddressYesNo, MemoryStream attachment = null, string attachmentFilename = null)
    {
        bool successYesNo = true;

        try
        {
            MailjetClient client = new MailjetClient("xxxxxx", "xxxxx")
            {
                Version = ApiVersion.V3_1,
            };
            MailjetRequest request = new MailjetRequest
                {
                    Resource = Send.Resource,
                }
                .Property(Send.Messages, new JArray {
                    new JObject {
                        {"From", new JObject {
                            {"Email", "xxxxx@xxxxx.com"},
                            {"Name", "xxxxx"}
                        }},
                        {"To", new JArray {
                            new JObject {
                                {"Email", toAddress},
                                {"Name", toAddress}
                            }
                        }},
                        {"Subject", subject},
                        {"TextPart", messageBody},
                        {"HTMLPart", messageBody}
                    }
                });
            MailjetResponse response = await client.PostAsync(request);
            if (response.IsSuccessStatusCode) // I never get to this point
            {
              :

我正在使用这个调用代码....

        if (Utility.SendEmailWithAttachment("xxxxx@xxxxx.com", "Test Email", "Test Body", false, false,
                po, "AAA.pdf").Result == false)
        {
            lblStatus.Text = "Email send failure. Please contact support.";
            return false;
        }

有趣的是,当我 运行 示例 mailjet 提供的代码时,我的电子邮件发送正常并且我确实到达了 PostAsync 之后的行。据我所知,唯一的主要区别是我使用的是返回布尔值的任务,而不仅仅是任务。这是 mailjet 提供的工作正常的代码....

    static void Main(string[] args)
    {
        RunAsync().Wait();
    }
    static async Task RunAsync()
    {
        MailjetClient client = new MailjetClient("xxxx", "xxxx")
        {
            Version = ApiVersion.V3_1,
        };
        MailjetRequest request = new MailjetRequest
            {
                Resource = Send.Resource,
            }
            .Property(Send.Messages, new JArray {
                new JObject {
                    {"From", new JObject {
                        {"Email", "xxxx@xxxx.com"},
                        {"Name", "xxxx"}
                    }},
                    {"To", new JArray {
                        new JObject {
                            {"Email", "xxxx@xxxx.com"},
                            {"Name", "xxxx"}
                        }
                    }},
                    {"Subject", "Your email flight plan!"},
                    {"TextPart", "Dear passenger 1, welcome to Mailjet! May the delivery force be with you!"},
                    {"HTMLPart", "<h3>Dear passenger 1, welcome to <a href='https://www.mailjet.com/'>Mailjet</a>!</h3><br />May the delivery force be with you!"}
                }
            });
        MailjetResponse response = await client.PostAsync(request);
        if (response.IsSuccessStatusCode) // this line is reached!
        {

提前致谢!

试试下面的代码。一个好的经验法则是不要使用 *.Result 除非先等待。

if ((await Utility.SendEmailWithAttachment("xxxxx@xxxxx.com", "Test Email", "Test Body", false, false,
            po, "AAA.pdf")) == false)
{
    lblStatus.Text = "Email send failure. Please contact support.";
    return false;
}

方法中

public static async Task<bool> SendEmailWithAttachment(string toAddress, string subject, string messageBody, bool sendBCCYesNo, bool sendFromInfoAddressYesNo, MemoryStream attachment = null, string attachmentFilename = null)

更改以下行,来自:

MailjetResponse response = await client.PostAsync(request);

至:

MailjetResponse response = await client.PostAsync(request).ConfigureAwait(false);

请阅读这篇关于异步方法死锁的精彩article

问题是由对 .Result 的调用引起的 - 这是一个阻塞调用。如果在 UI 线程中调用它,它将阻塞线程。这意味着应用程序通常无法响应并出现冻结状态,这非常难看。

这也意味着任何 await 尝试在 UI 线程上恢复的调用,如

MailjetResponse response = await client.PostAsync(request);

不会。我再重复一遍 - 如果 await client.PostAsync(request); 似乎没有恢复,那是因为 某些东西 正在阻塞 UI 线程。

await 不会异步执行任何操作 运行,也不会启动任何线程。它 等待 已经 运行 宁非阻塞的异步操作。当这些完成后,它会在原始同步上下文中恢复——在桌面应用程序中,这意味着在 UI 线程上恢复。这就是允许 await 之后的任何代码修改 UI.

的原因

解决方案是删除 .Result。没有它,无论如何使用异步调用都是毫无意义的——整个应用程序挂起,那么等待有什么意义?

假设在事件处理程序中调用该方法,事件处理程序本身应该变为异步。代码应该稍微清理一下。这不会影响异步行为,但会使其更易于阅读和调试:

private async void button1_Click(...)
{
    var ok=await Utility.SendEmailWithAttachment("xxxxx@xxxxx.com", "Test Email", "Test Body", 
                                                   false, false,po, "AAA.pdf");
    if (!ok)
    {
        lblStatus.Text = "Email send failure. Please contact support.";
    }
}

如果该方法不是事件处理程序,则应更改为异步方法。它的调用者也应该成为一个异步方法,一直到顶部 - 大多数时候,这是一个事件处理程序:

private async Task<bool> TrySend()
{
    var ok=await Utility.SendEmailWithAttachment("xxxxx@xxxxx.com", "Test Email", "Test Body", 
                                                   false, false,po, "AAA.pdf");
    if (!ok)
    {
        lblStatus.Text = "Email send failure. Please contact support.";
        return false;
    }
    else
    {
        .....
        return true;
    }
}

private async void button1_Click(...)
{
    var ok=await TrySend();
    ...
}

SendEmailWithAttachment 本身不会尝试修改 UI,因此它 不需要 在 UI 线程上恢复。添加 ConfigureAwait(false) 将允许代码在线程池线程上恢复,并让 调用者 决定是否在 UI 上恢复。这主要是此时的优化,但是也去掉了原来死锁中的二次阻塞点。如果有人错误地添加回 .Result,它会 "only" 冻结 UI :

MailjetResponse response = await client.PostAsync(request).ConfigureAwait(false);