Код зависает после отправки электронной почты, но электронная почта отправлена ​​в порядке (статическая асинхронная задача) - PullRequest
1 голос
/ 17 октября 2019

Я пишу код 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. Единственное основное отличие, как я могу сказать, в том, что я использую Task, возвращающий bool, а не просто Task. Вот код, предоставленный 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!
        {

Заранее спасибо!

Ответы [ 3 ]

4 голосов
/ 17 октября 2019

Попробуйте код ниже. Хорошее эмпирическое правило - не использовать * .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;
}
3 голосов
/ 17 октября 2019

В методе

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

Пожалуйста, прочитайте эту замечательную статью относительно взаимоблокировок в асинхронном методе

2 голосов
/ 17 октября 2019

Проблема вызвана вызовом .Result - это блокирующий вызов. Если это вызывается в потоке пользовательского интерфейса, он заблокирует поток. Это означает, что приложение не сможет отвечать вообще и выглядеть замороженным, что-то довольно уродливое.

Это также означает, что любые await вызовы, которые пытаются возобновить в потоке пользовательского интерфейса, например

MailjetResponse response = await client.PostAsync(request);

, не смогут. Я повторю это - если await client.PostAsync(request); не возобновляется, это потому, что что-то блокирует поток пользовательского интерфейса.

await не запускает ничего асинхронно и не запускает никакие потоки. ожидает уже выполняющихся асинхронных операций без блокировки. Когда они заканчиваются, он возобновляется в исходном контексте синхронизации - в настольном приложении, что означает возобновление в потоке пользовательского интерфейса. Это то, что позволяет любому коду после await изменять пользовательский интерфейс.

Решение состоит в том, чтобы удалить .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 сам по себе не пытается изменить пользовательский интерфейс, поэтому он не't нужно для возобновления в потоке пользовательского интерфейса. Добавление ConfigureAwait(false) позволит возобновить код в потоке потоков и позволить вызывающей стороне решить, возобновлять ли его в пользовательском интерфейсе или нет. На данный момент это в основном оптимизация, но она также удаляет вторичную точку блокировки в исходном тупике. Если кто-то добавит обратно .Result по ошибке, он «только» заморозит пользовательский интерфейс:

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