Reputation: 1239
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", "[email protected]"},
{"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("[email protected]", "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", "[email protected]"},
{"Name", "xxxx"}
}},
{"To", new JArray {
new JObject {
{"Email", "[email protected]"},
{"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!
Upvotes: 1
Views: 701
Reputation: 131180
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("[email protected]", "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("[email protected]", "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);
Upvotes: 2
Reputation: 118
Try the code below. A good rule of thumb is not to use *.Result unless awaited first.
if ((await Utility.SendEmailWithAttachment("[email protected]", "Test Email", "Test Body", false, false,
po, "AAA.pdf")) == false)
{
lblStatus.Text = "Email send failure. Please contact support.";
return false;
}
Upvotes: 4
Reputation: 1319
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
Upvotes: 3