1

I'm writing some C# code to send an email (via Mailjet/Azure). It DOES send the email, but for some reason when stepping through the code I never get past this line of code....

MailjetResponse response = await client.PostAsync(request);

It just hangs at that point. Any idea why? Again, the email is being sent OK!

  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
            {
              :

I'm calling the code using this....

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

Interestingly, when I run the sample mailjet-provided code I the email is sent fine AND I DO reach the line after the PostAsync. The only main difference, as I can tell, is that I'm using Task returning bool rather than just Task. Here's the mailjet-provided code which works fine....

    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!
        {

Thanks in advance!

Stas Ivanov
  • 1,173
  • 1
  • 14
  • 24
WebDevGuy2
  • 1,159
  • 1
  • 19
  • 40
  • And how do you know that you will never pass this line of code? Debugin ?? – Ahmed Msaouri Oct 17 '19 at 14:30
  • Could you please show the code which calls SendEmailWithAttachment? If it's some kind of UI app (WinForms, WPF, UWP) you could try doing "await client.PostAsync(request).ConfigureAwait(false)". – Stas Ivanov Oct 17 '19 at 14:31
  • @AhmedMsaouri easy. I set a breakpoint and stepped through the code and the next line is never reached. – WebDevGuy2 Oct 17 '19 at 14:35
  • 1
    @StasIvanov I updated the op. – WebDevGuy2 Oct 17 '19 at 14:35
  • That's why I was asking, when debugging you can't be 100% sure if it goes through a line or not, especially if it runs on another deferential thread, you can create a console.print of the object to see if it really happens or not – Ahmed Msaouri Oct 17 '19 at 14:38
  • the await reserved word creates another thread different from the main one, and it may be when debugging the code you cannot see the real state of the object – Ahmed Msaouri Oct 17 '19 at 14:39
  • @AhmedMsaouri thanks. What you said does make sense but please see my updated OP. Do you see the code mailjet provided? This works fine. And I can step nicely between all the lines. I hope I'm making sense. Also adding ".ConfigureAwait(false)" doesn't fix it. – WebDevGuy2 Oct 17 '19 at 14:45
  • 1
    @AhmedMsaouri no it doesn't. `await` doesn't start or run anything, it *awaits* an already running operation without blocking the calling thread. – Panagiotis Kanavos Oct 17 '19 at 14:56
  • 1
    @WebDevGuy2 start by *removing* all blocking calls like `.Result` or `.Wait()` and use `await` instead. `.Result` *blocks*. If you do that on the UI thread, the app itself blocks. That means that any other async operations that were being awaited won't be able to resume on the UI thread – Panagiotis Kanavos Oct 17 '19 at 14:58
  • 1
    @WebDevGuy2 where do you make the call to `if (Utility.SendEmailWithAttachment...`? The call should change to `if (await SendEmailWithAttachment(..))` at least. The method should be marked with `async`. If this is an event handler, it should be `async void`. *Any* other method should use `async Task`. – Panagiotis Kanavos Oct 17 '19 at 15:00
  • 1
    @WebDevGuy2 right now, that `.Result` blocks the UI thread. The call to `await client.PostAsync(request);` will try to resume on that thread, which is already blocked, causing a deadlock. Simply removing that `.Result` and using `await` instead will remove the deadlock *and* allow the UI to respond to other events. Using `ConfigureAwait(false)` inside `SendEmailWithAttachment` means `await` won't try to resume on the UI thread - this will also prevent the deadlock but won't release the UI thread. – Panagiotis Kanavos Oct 17 '19 at 15:03
  • 1
    Just FYI: await keyword does NOT create a separate thread. – Stas Ivanov Oct 17 '19 at 15:20

3 Answers3

4

Try the code below. A good rule of thumb is not to use *.Result unless awaited first.

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

In the method

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

Change following line, from:

MailjetResponse response = await client.PostAsync(request);

to:

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

Please read this great article regarding deadlocks in async method

Szymon Tomczyk
  • 1,219
  • 11
  • 16
2

The problem is caused by the call to .Result - this is a blocking call. If that is called in the UI thread, it will block the thread. This means the application won't be able to respond in general and appear frozen, something rather ugly.

It also means that any await calls that try to resume on the UI thread like

MailjetResponse response = await client.PostAsync(request);

won't be able to. I'll repeat this - if await client.PostAsync(request); doesn't seem to resume, it's because something is blocking the UI thread.

await doesn't make anything run asynchronously, nor does it start any threads. It awaits already running asynchronous operations without blocking. When those finish, it resumes in the original synchronization context - in a desktop application, that means resuming on the UI thread. That's what allows any code after await to modify the UI.

The solution is to remove .Result. Without it it's pointless to use asynchronous calls anyway - the entire application hangs, so what's the point of awaiting?

Assuming the method is called in an event handler, the event handler itself should become async. The code should be cleaned up a little. This won't affect the async behavior, but makes it easier to read and debug :

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.";
    }
}

If the method isn't an event handler, it should change into an async method. Its caller should become an async method too, all the way to the top - most of the time, that's an event handler :

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 itself doesn't try to modify the UI so it doesn't need to resume on the UI thread. Adding ConfigureAwait(false) will allow the code to resume on a threadpool thread and let the caller decide whether to resume on the UI or not. This is mainly an optimization at this point, but it also removes the secondary blocking point in the original deadlock. If someone adds back .Result by mistake, it will "only" freeze the UI :

MailjetResponse response = await client.PostAsync(request).ConfigureAwait(false);
Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236
  • So I changed the call to the method to use await and not result. Then added .ConfigureAwait(false) to my PostAsync. When I run this line the UI resumes BUT I get an "object not set" message now and I can't even address any results of the PostAsync. – WebDevGuy2 Oct 17 '19 at 15:34
  • That' means there are other problems in the rest of the code that wasn't posted in the question - you're trying to access a variable that wasn't initialized. The problem you asked about though seems to be solved – Panagiotis Kanavos Oct 17 '19 at 16:09