For fastest learning..
Understand method execution flow(with a diagram): 3 mins
Question introspection (learning sake): 1 min
Quickly get through syntax sugar: 5 mins
Share the confusion of a developer : 5 mins
Problem: Quickly change a real-world implementation of normal code to
Async code: 2 mins
Where to Next?
Understand method execution flow(with a diagram): 3 mins
In this image, just focus on #6 (nothing more)
At #6 step, execution ran out of work and stopped. To continue it needs a result from getStringTask(kind of a function). Therefore, it uses an await
operator to suspend its progress and give control back(yield) to the caller(of this method we are in). The actual call to getStringTask was made earlier in #2. At #2 a promise was made to return a string result. But when will it return the result? Should we(#1:AccessTheWebAsync) make a 2nd call again? Who gets the result, #2(calling statement) or #6(awaiting statement)?
The external caller of AccessTheWebAsync() also is waiting now. So caller waiting for AccessTheWebAsync, and AccessTheWebAsync is waiting for GetStringAsync at the moment. Interesting thing is AccessTheWebAsync did some work(#4) before waiting perhaps to save time from waiting. The same freedom to multitask is also available for the external caller(and all callers in the chain) and this is the biggest plus of this 'async' thingy! You feel like it is synchronous..or normal but it is not.
#2 and #6 is split so we have the advantage of #4(work while waiting). But we can also do it without splitting. So #2 will be: string urlContents = await client.GetStringAsync("...");
. Here we see no advantage but somewhere in the chain one function will be splitting while rest of them call it without splitting. It depends which function/class in the chain you use. This change in behavior from function to function is the most confusing part about this topic.
Remember, the method was already returned(#2), it cannot return again(no second time). So how will the caller know? It is all about Tasks! Task was returned. Task status was waited for (not method, not value). Value will be set in Task. Task status will be set to complete. Caller just monitors Task(#6). So 6# is the answer to where/who gets the result. Further reads for later here.
Question introspection for learning sake: 1 min
Let us adjust the question a bit:
How and When to use async
and await
Tasks
?
Because learning Task
automatically covers the other two(and answers your question).
The whole idea is pretty simple. A method can return any data type(double, int, object, etc.) but here it is denied and a 'Task
' object return is enforced! But we still need the returned data(except void), right? That will be set in a standard property inside 'Task
' object eg: 'Result
' property. Then we box/unbox(convert to our data type of choice).
Quickly get through syntax sugar: 5 mins
- Original non-async method
internal static int Method(int arg0, int arg1)
{
int result = arg0 + arg1;
IO(); // Do some long running IO.
return result;
}
- a brand new Task-ified method to call the above method
internal static Task<int> MethodTask(int arg0, int arg1)
{
Task<int> task = new Task<int>(() => Method(arg0, arg1));
task.Start(); // Hot task (started task) should always be returned.
return task;
}
Did we mention await or async? No. Call the above method and you get a task which you can monitor. You already know what the task returns(or contains).. an integer.
- Calling a Task is slightly tricky and that is when the keywords starts to appear. If there was a method calling the original method(non-async) then we need to edit it as given below. Let us call MethodTask()
internal static async Task<int> MethodAsync(int arg0, int arg1)
{
int result = await HelperMethods.MethodTask(arg0, arg1);
return result;
}
Same code above added as image below:
- We are 'awaiting' task to be finished. Hence the
await
(mandatory syntax)
- Since we use await, we must use
async
(mandatory syntax)
- MethodAsync with
Async
as the prefix (coding standard)
await
is easy to understand but the remaining two (async
,Async
) may not be :). Well, it should make a lot more sense to the compiler though.Further reads for later here
So there are 2 parts.
Create 'Task' (only one task and it will be an additional method)
Create syntactic sugar to call the task with await+async
(this involves changing existing code if you are converting a non-async method)
Remember, we had an external caller to AccessTheWebAsync() and that caller is not spared either... i.e it needs the same await+async
too. And the chain continues(hence this is a breaking change which could affect many classes). It can also be considered a non-breaking change because the original method is still there to be called. Change it's access (or delete and move it inside a task) if you want to impose a breaking change and then the classes will be forced to use Task-method. Anyways, in an async call there will always be a Task
at one end and only one.
All okay, but one developer was surprised to see Task
missing...
Share the confusion of a developer: 5 mins
A developer has made a mistake of not implementing Task
but it still works! Try to understand the question and just the accepted answer provided here. Hope you have read and fully understood. The summary is that we may not see/implement 'Task' but it is implemented somewhere in a parent/associated class. Likewise in our example calling an already built MethodAsync()
is way easier than implementing that method with a Task
(MethodTask()
) ourself. Most developers find it difficult to get their head around Tasks
while converting a code to Asynchronous one.
Tip: Try to find an existing Async implementation (like MethodAsync
or ToListAsync
) to outsource the difficulty. So we only need to deal with Async and await (which is easy and pretty similar to normal code)
Problem: Quickly change a real-world implementation of normal code to
Async operation: 2 mins
Code line shown below in Data Layer started to break(many places). Because we updated some of our code from .Net framework 4.2.* to .Net core. We had to fix this in 1 hour all over the application!
var myContract = query.Where(c => c.ContractID == _contractID).First();
easypeasy!
- We installed EntityFramework nuget package because it has QueryableExtensions. Or in other words it does the Async implementation(task), so we could survive with simple
Async
and await
in code.
- namespace = Microsoft.EntityFrameworkCore
calling code line got changed like this
var myContract = await query.Where(c => c.ContractID == _contractID).FirstAsync();
- Method signature changed from
Contract GetContract(int contractnumber)
to
async Task<Contract> GetContractAsync(int contractnumber)
- calling method also got affected:
GetContract(123456);
was called as GetContractAsync(123456).Result;
Wait! what is that Result
? Good catch! GetContractAsync
only returns a Task
not the value we wanted(Contract
). Once the result of an operation is available, it is stored and is returned immediately on subsequent calls to the Result
property.
We can also do a time-out implementation with a similar 'Wait()'
TimeSpan ts = TimeSpan.FromMilliseconds(150);
if (! t.Wait(ts))
Console.WriteLine("The timeout interval elapsed.");
- We changed it everywhere in 30 minutes!
But the architect told us not to use EntityFramework library just for this! oops! drama! Then we made a custom Task implementation(yuk!). Which you know how. Still easy! ..still yuk..
Where to Next?
There is a wonderful quick video we could watch about Converting Synchronous Calls to Asynchronous in ASP.Net Core. Or have I explained enough? ;)
await
.sub example { my $p = do-something-async; say 'next line'; await $p; say 'done awaiting'}; sub do-something-async { return Promise.in(5).then: {say 'promise done'}}; example()
. Which would printnext line
. Then after 5 secondspromise done
. Followed shortly bydone awaiting
. – Chickaree