How and when to use ‘async’ and ‘await’

前端 未结 21 1831
你的背包
你的背包 2020-11-21 05:07

From my understanding one of the main things that async and await do is to make code easy to write and read - but is using them equal to spawning background threads to perfo

21条回答
  •  庸人自扰
    2020-11-21 05:15

    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. 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.

    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 passed. Task 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)

    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 MethodTask(int arg0, int arg1)
        {
            Task task = new Task(() => 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.. 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-asyc) then we are going to change it. Let us call MethodTask()
    internal static async Task MethodAsync(int arg0, int arg1)
        {
            int result = await HelperMethods.MethodTask(arg0, arg1);
            return result;
        }
    

    Same code above added as image below:

    1. We are 'awaiting' task to be finished. Hence the await
    2. Since we use await, we must use async(mandatory syntax)
    3. 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.

    1. Create 'Task' (only one task and it will be an additional method)

    2. 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 if you want to impose a breaking change and then the classes will be forced to use Task-method. Or just delete the method and move it to 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!

    1. 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.
    2. namespace = Microsoft.EntityFrameworkCore

    calling code line got changed like this

    var myContract = await query.Where(c => c.ContractID == _contractID).FirstAsync();
    
    1. Method signature changed from

    Contract GetContract(int contractnumber)

    to

    async Task GetContractAsync(int contractnumber)

    1. calling method also got affected: GetContractAsync(123456); was called as GetContractAsync(123456).Result;

    2. 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, perhaps that is likely the direction one would go after reading this. Or have I explained enough? ;)

提交回复
热议问题