I'm trying to understand async await in the simplest form. I want to create a very simple method that adds two numbers for the sake of this example, granted, it's no processing time at all, it's just a matter of formulating an example here.

3 Answers
3

First, let's clear up some terminology: "asynchronous" (async) means that it may yield control back to the calling thread before it starts. In an async method, those "yield" points are await expressions.

This is very different than the term "asynchronous", as (mis)used by the MSDN documentation for years to mean "executes on a background thread".

To futher confuse the issue, async is very different than "awaitable"; there are some async methods whose return types are not awaitable, and many methods returning awaitable types that are not async.

So, the basic pattern of things is to have async code depend on "awaitables" in its await expressions. These "awaitables" can be other async methods or just regular methods returning awaitables. Regular methods returning Task/Task<T>can use Task.Run to execute code on a background thread, or (more commonly) they can use TaskCompletionSource<T> or one of its shortcuts (TaskFactory.FromAsync, Task.FromResult, etc). I don't recommend wrapping an entire method in Task.Run; synchronous methods should have synchronous signatures, and it should be left up to the consumer whether it should be wrapped in a Task.Run:

@TopinFrassi: Yes, they will compile, but void is not awaitable.
– Stephen ClearyNov 21 '14 at 21:17

3

@ohadinho: No, what I'm talking about in the blog post is when the entire method is just a call to Task.Run (like DoWorkAsync in this answer). Using Task.Run to call a method from a UI context is appropriate (like DoVariousThingsFromTheUIThreadAsync).
– Stephen ClearyFeb 16 '17 at 14:15

2

Yes, exactly. It's valid to use Task.Run to invoke a method, but if there's a Task.Run around all (or almost all) of the method's code, then that's an anti-pattern - just keep that method synchronous and move the Task.Run up a level.
– Stephen ClearyJun 8 '17 at 19:32

One of the most important thing to remember when decorating a method with async is that at least there is oneawait operator inside the method. In your example, I would translate it as shown below using TaskCompletionSource.

Why do you use TaskCompletionSource, instead of just returning task returned by Task.Run() method (and changing its body to return result)?
– ironicJun 21 '16 at 13:39

3

Just a side note. A method that has a "async void" signature is generally bad practice and considered bad code as it can lead to UI deadlock pretty easily. The main exception is asynchronous event handlers.
– JazzerokiOct 28 '16 at 14:33

When you use Task.Run to run a method, Task gets a thread from threadpool to run that method. So from the UI thread's perspective, it is "asynchronous" as it doesn't block UI thread.This is fine for desktop application as you usually don't need many threads to take care of user interactions.

However, for web application each request is serviced by a thread-pool thread and thus the number of active requests can be increased by saving such threads. Frequently using threadpool threads to simulate async operation is not scalable for web applications.

So if I have to consume a synchronous external api within a web api controller, I should NOT wrap the synchronous call in Task.Run()? As you said, doing so will keep the initial request thread unblocked but it's using another pool thread to call the external api. In fact I think it's still a good idea because doing this way it can in theory use two pool threads to process many requests e.g. one thread can process many incoming requests and another one can call the external api for all these requests?
– stt106Mar 15 '17 at 23:31

@stt106 I should NOT wrap the synchronous call in Task.Run() that's correct. If you do, you'd just be switching threads. i.e. you're unblocking the initial request thread but you're taking another thread from the threadpool which could have been used to process another request. The only outcome is a context switch overhead when the call is completed for absolutely zero gain
– Saeb AminiAug 10 '17 at 23:41