1

I have a application where I use Async and Await when calling a rest web service. When running my unit tests, I can't seem to get any proper response back even though I am using await. This is the async method:

public async Task<Response> SendEmail(string apiKey, string senderEmail, string senderName, string recipientEmail, string recipientName, string subject, string content, bool html)
{
    var client = new SendGridClient(apiKey);
    var from = new EmailAddress(senderEmail, senderName);
    var to = new EmailAddress(recipientEmail, recipientName);
    var msg = MailHelper.CreateSingleEmail(from, to, subject, html ? null : content, html ? content : null);
    var response = await client.SendEmailAsync(msg);

    return response;
}

The calling method looks like this:

public static object SendEmail(string apiKey, string senderEmail, string senderName, string recipientEmail, string recipientName, string subject, string content, bool html)
{
    EmailHandler emailHandler = new EmailHandler();
    var response =  emailHandler.SendEmail(apiKey, senderEmail, senderName, recipientEmail, recipientName, subject, content, html);

    return response;
}

Now if I put a breakpoint on return response in the calling function, I can see an object that has the status="Waiting for Activation" and Result="Not yet computed". Of what I have been able to gather, calling .Result on the returned object should make it run synchronously and return the result. (For example, status code of the request such as 200).

What am I missing here? Why does it not wait until it is finished?

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
kalle1
  • 43
  • 1
  • 5
  • You need to make `SendEmail` async. It appears like your deadlocked – Liam Feb 22 '19 at 09:06
  • 2
    The response that you return from SendEmail is a Task that hasen't completed yet. Instead of using `object` and `var` try specifying the types explicitly. That way you'll get compilation errors when you're not working with the types you think you are. – Hans Kilian Feb 22 '19 at 09:06
  • How is `SendEmailAsync` defined? – Uwe Keim Feb 22 '19 at 09:06
  • Possible duplicate of [How would I run an async Task method synchronously?](https://stackoverflow.com/questions/5095183/how-would-i-run-an-async-taskt-method-synchronously) – Liam Feb 22 '19 at 09:07
  • @HansKilian `var` does specify the types explicitly. Neither it nor `object` is a problem here. – GSerg Feb 22 '19 at 09:09
  • @HansKilian you'll get compilation errors with `var` because it infers the type at compile time. `dynamic` is where you wouldn't get exceptions because that's resolved at runtime, but that's a topic for another day. – ColinM Feb 22 '19 at 09:50
  • 1
    @GSerg Having `object` as the return type means that it's ok to return a `Response` or a `Task`. If the OP had made the method return `Response` then it would not compile. As for `var` I guess Hans means if they used `Response` instead of `var` that also would give a compilation error. – juharr Feb 22 '19 at 10:07
  • @kalle1: A couple comments. 1) `Task.Status` is misleading; [since this is a Promise Task, "Waiting for Activation" actually means "in progress"](https://blog.stephencleary.com/2014/06/a-tour-of-task-part-3-status.html). 2) `Result` does not mean "make it run synchronously"; the task is already in progress as soon as `SendEmail` returns; `Result` means "block this thread until the task is complete", which [can cause deadlocks](https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html). – Stephen Cleary Feb 22 '19 at 14:17

3 Answers3

5

async-await are just keywords which tell the compiler to compile your code as an implementation of an IAsyncStateMachine and also wait for it to finish, both keywords need to be used in conjunction for this to work and they only work on Task objects. A Task represents a piece of work that is happening.

You must mark your SendEmail method as async, change the return type to Task<object> and await your emailHandler.SendEmail call.

Bottom line is, if you're going async then you must go async all the way up and await at some or many points, otherwise you begin looking at running asynchronous code synchronously which is kind of shooting yourself in the foot.

public static async Task<object> SendEmail(string apiKey, string senderEmail, string senderName, string recipientEmail, string recipientName, string subject, string content, bool html)
{
    EmailHandler emailHandler = new EmailHandler();
    var response = await emailHandler.SendEmail(apiKey, senderEmail, senderName, recipientEmail, recipientName, subject, content, html);

    return response;
}

As always, Stephen Cleary is an excellent source for async knowledge.

ColinM
  • 2,622
  • 17
  • 29
  • I see. I made the SendEmail method async Task as well and in my unit test I have to await the result as well. I believed it was enough to await the result in one location, not the entire chain. – kalle1 Feb 22 '19 at 09:37
  • The reason you're awaiting the result in the entire chain is because you're first awaiting the result of `SendEmail`, but further up the chain you're awaiting for this `static` `SendEmail` member to finish waiting for its dependency to finish, and so on. It's all chained together and you have to wait for each link to finish its work, or fire & forget. – ColinM Feb 22 '19 at 09:40
  • FYI, you can see what `async` code compiles into, along with how its executed and what the state machine looks like on [SharpLab](https://sharplab.io/#v2:CYLg1APgAgDABFAjAbgLAChYMQVjZgZgQCY4BhOAbwzloSKgA4EA2OAWQAoBKKmugUgCcnAEQAhAKYAzAPYAnSaO74BgoawB0AEUkAbAIYBPTjhjmV/NdhGiAgtIAuk+ctV0Avhg9A==) – ColinM Feb 22 '19 at 09:43
  • Ok, but that doesn't seem to work. If I go with your send suggestion (the one that does not await) I get the same result as before, i.e the task is not done when it is returning the response. – kalle1 Feb 22 '19 at 09:44
  • 1
    That's because you need to `await` the method further up. In the second suggestion it doesn't wait for `SendGridClient` to send the email, it directly returns the `Task` or work where `SendGridClient` is sending the email. This is more down to choice, I personally like to return Tasks rather than await on lower level `async` methods like `HttpClient` wrappers etc. Bottom line is, when you use `Task` you **need** to wait for it to finish - ideally with `await` otherwise you'd risk performance and scalability issues. – ColinM Feb 22 '19 at 09:46
  • Sorry, I'm not following. I am still calling await on the client: " var response = await client.SendEmailAsync(msg);" Should the program block on that await until it is finished? – kalle1 Feb 22 '19 at 09:50
  • I have removed my second option for simplicity. The program should never "block", that's not what `async`-`await` does. You need to await everywhere where a `Task` is returned, in both of these methods and the methods above. – ColinM Feb 22 '19 at 09:52
  • 1
    Ok, I think I might understand. What I meant by "block" is that the program needs to wait until it gets the result from a long running block of code as the next statement depends on it. – kalle1 Feb 22 '19 at 09:55
  • 1
    Think if it in the same context as your unit test, you need to wait for that with `await` or `.Result` - **which I'd recommend only doing in unit tests** - before you can assert on the result/outcome. In your production code you'll eventually have to wait for the `Task` chain to finish executing to handle the response. – ColinM Feb 22 '19 at 09:59
  • 1
    All unit test frameworks have supported `async Test` unit test methods for years now; there's no need to block anymore, even in the unit test. – Stephen Cleary Feb 22 '19 at 14:10
2

It's because

var response =  emailHandler.SendEmail(apiKey, senderEmail, senderName, recipientEmail, recipientName, subject, content, html);

initiaties a Task, and your caller (public static object SendEmail) doesn't await that task.

Chris Pratt has written a nice blog with an async-helper to run async-methods sync.

BerDev
  • 1,457
  • 1
  • 8
  • 16
-4

It seems to me that you expect SendEmail to be synchronous. You can wait for the asynchronous method to complete in SendEmail like this:

public static Response SendEmail(string apiKey, string senderEmail, string senderName, string recipientEmail, string recipientName, string subject, string content, bool html)
{
    EmailHandler emailHandler = new EmailHandler();
    var responseTask =  emailHandler.SendEmail(apiKey, senderEmail, senderName, recipientEmail, recipientName, subject, content, html);
    responseTask.Wait(); // Wait for the task to complete

    return responseTask.Result;
}
Hans Kilian
  • 18,948
  • 1
  • 26
  • 35