【问题标题】:Code hangs after sending email but email is sent OK (static async Task)发送电子邮件后代码挂起,但电子邮件发送正常(静态异步任务)
【发布时间】:2019-10-17 14:25:21
【问题描述】:

我正在编写一些 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!
        {

提前致谢!

【问题讨论】:

  • @StasIvanov 我更新了操作。
  • @AhmedMsaouri 不,它没有。 await 不会启动或运行任何东西,它等待一个已经在运行的操作而不阻塞调用线程。
  • @WebDevGuy2 首先删除所有阻塞调用,如.Result.Wait(),并改用await.Result 。如果您在 UI 线程上执行此操作,则应用程序本身会阻塞。这意味着任何其他正在等待的异步操作将无法在 UI 线程上恢复
  • @WebDevGuy2 你在哪里打电话给if (Utility.SendEmailWithAttachment...?呼叫至少应更改为if (await SendEmailWithAttachment(..))。该方法应标有async。如果这是一个事件处理程序,它应该是async void任何其他方法都应该使用async Task
  • @WebDevGuy2 现在,.Result 阻塞了 UI 线程。对await client.PostAsync(request); 的调用将尝试在该线程上恢复,该线程已被阻塞,从而导致死锁。只需删除 .Result 并改用 await 将删除死锁 允许 UI 响应其他事件。在SendEmailWithAttachment 中使用ConfigureAwait(false) 意味着await 不会尝试在UI 线程上恢复——这也将防止死锁但不会释放UI 线程。

标签: c# multithreading async-await task mailjet


【解决方案1】:

试试下面的代码。一个好的经验法则是,除非先等待,否则不要使用 *.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;
}

【讨论】:

  • 谢谢。但我什至没有得到任何回报声明。而且,当我删除 *.Result 它不会编译。返回类型为“Task”。
  • 你在开始时添加了等待吗? if (await Utilit....
  • @TomislavBalijint 是的
  • @WebDevGuy2 use await and 删除 .Result 将解决问题 - 除非在其余代码中有 另一个 阻塞调用。您还没有发布如何调用该方法,这意味着不知何故,某处,还有另一个阻塞调用来获取bool
  • @PanagiotisKanavos 我使用了 await 并删除了结果。我还发布了我如何调用该方法。可能是我误会了?
【解决方案2】:

在方法中

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

【讨论】:

    【解决方案3】:

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

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

    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,它只会“冻结”用户界面:

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

    【讨论】:

    • 所以我改变了对方法的调用以使用等待而不是结果。然后将 .ConfigureAwait(false) 添加到我的 PostAsync 中。当我运行此行时,UI 恢复,但我现在收到“对象未设置”消息,我什至无法解决 PostAsync 的任何结果。
    • 这意味着问题中未发布的其余代码中存在其他问题 - 您正在尝试访问未初始化的变量。你问的问题好像已经解决了
    猜你喜欢
    • 1970-01-01
    • 2012-01-06
    • 1970-01-01
    • 2012-03-21
    • 2011-04-08
    • 1970-01-01
    • 2011-05-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多