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

前端 未结 21 1789
你的背包
你的背包 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:12

    All the answers here use Task.Delay() or some other built in async function. But here is my example that use none of those async functions:

    // Starts counting to a large number and then immediately displays message "I'm counting...". 
    // Then it waits for task to finish and displays "finished, press any key".
    static void asyncTest ()
    {
        Console.WriteLine("Started asyncTest()");
        Task<long> task = asyncTest_count();
        Console.WriteLine("Started counting, please wait...");
        task.Wait(); // if you comment this line you will see that message "Finished counting" will be displayed before we actually finished counting.
        //Console.WriteLine("Finished counting to " + task.Result.ToString()); // using task.Result seems to also call task.Wait().
        Console.WriteLine("Finished counting.");
        Console.WriteLine("Press any key to exit program.");
        Console.ReadLine();
    }
    
    static async Task<long> asyncTest_count()
    {
        long k = 0;
        Console.WriteLine("Started asyncTest_count()");
        await Task.Run(() =>
        {
            long countTo = 100000000;
            int prevPercentDone = -1;
            for (long i = 0; i <= countTo; i++)
            {
                int percentDone = (int)(100 * (i / (double)countTo));
                if (percentDone != prevPercentDone)
                {
                    prevPercentDone = percentDone;
                    Console.Write(percentDone.ToString() + "% ");
                }
    
                k = i;
            }
        });
        Console.WriteLine("");
        Console.WriteLine("Finished asyncTest_count()");
        return k;
    }
    
    0 讨论(0)
  • 2020-11-21 05:15

    Explanation

    Here is a quick example of async/await at a high level. There are a lot more details to consider beyond this.

    Note: Task.Delay(1000) simulates doing work for 1 second. I think it's best to think of this as waiting for a response from an external resource. Since our code is waiting for a response, the system can set the running task off to the side and come back to it once it's finished. Meanwhile, it can do some other work on that thread.

    In the example below, the first block is doing exactly that. It starts all the tasks immediately (the Task.Delay lines) and sets them off to the side. The code will pause on the await a line until the 1 second delay is done before going to the next line. Since b, c, d, and e all started executing at almost the exact same time as a (due to lack of the await), they should finish at roughly the same time in this case.

    In the example below, the second block is starting a task and waiting for it to finish (that is what await does) before starting the subsequent tasks. Each iteration of this takes 1 second. The await is pausing the program and waiting for the result before continuing. This is the main difference between the first and second blocks.

    Example

    Console.WriteLine(DateTime.Now);
    
    // This block takes 1 second to run because all
    // 5 tasks are running simultaneously
    {
        var a = Task.Delay(1000);
        var b = Task.Delay(1000);
        var c = Task.Delay(1000);
        var d = Task.Delay(1000);
        var e = Task.Delay(1000);
    
        await a;
        await b;
        await c;
        await d;
        await e;
    }
    
    Console.WriteLine(DateTime.Now);
    
    // This block takes 5 seconds to run because each "await"
    // pauses the code until the task finishes
    {
        await Task.Delay(1000);
        await Task.Delay(1000);
        await Task.Delay(1000);
        await Task.Delay(1000);
        await Task.Delay(1000);
    }
    Console.WriteLine(DateTime.Now);
    

    OUTPUT:

    5/24/2017 2:22:50 PM
    5/24/2017 2:22:51 PM (First block took 1 second)
    5/24/2017 2:22:56 PM (Second block took 5 seconds)
    

    Extra info regarding SynchronizationContext

    Note: This is where things get a little foggy for me, so if I'm wrong on anything, please correct me and I will update the answer. It's important to have a basic understanding of how this works but you can get by without being an expert on it as long as you never use ConfigureAwait(false), although you will likely lose out on some opportunity for optimization, I assume.

    There is one aspect of this which makes the async/await concept somewhat trickier to grasp. That's the fact that in this example, this is all happening on the same thread (or at least what appears to be the same thread in regards to its SynchronizationContext). By default, await will restore the synchronization context of the original thread that it was running on. For example, in ASP.NET you have an HttpContext which is tied to a thread when a request comes in. This context contains things specific to the original Http request such as the original Request object which has things like language, IP address, headers, etc. If you switch threads halfway through processing something, you could potentially end up trying to pull information out of this object on a different HttpContext which could be disastrous. If you know you won't be using the context for anything, you can choose to "not care" about it. This basically allows your code to run on a separate thread without bringing the context around with it.

    How do you achieve this? By default, the await a; code actually is making an assumption that you DO want to capture and restore the context:

    await a; //Same as the line below
    await a.ConfigureAwait(true);
    

    If you want to allow the main code to continue on a new thread without the original context, you simply use false instead of true so it knows it doesn't need to restore the context.

    await a.ConfigureAwait(false);
    

    After the program is done being paused, it will continue potentially on an entirely different thread with a different context. This is where the performance improvement would come from -- it could continue on on any available thread without having to restore the original context it started with.

    Is this stuff confusing? Hell yeah! Can you figure it out? Probably! Once you have a grasp of the concepts, then move on to Stephen Cleary's explanations which tend to be geared more toward someone with a technical understanding of async/await already.

    0 讨论(0)
  • 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<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.. 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<int> 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<Contract> 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? ;)

    0 讨论(0)
  • 2020-11-21 05:17

    Tasks let you control the number of threads you are running on.
    The optimal number of threads is the number of cores you have.

    • when your app is Console, WinForms or WPF then you are normally running on a single-thread.

      • in this case you want to increase the number of threads
      • your main tool is Task.Run(), and sometimes Parallel.ForERach()
      • doing synchronous I/O on a thread (with Task.Run()) is sub-optimal but acceptable
    • when your app runs on a Webserver it normally is running on (too) many threads.
      asp.net waits for incoming requests and quickly hands each one to a new pool thread.

      • in this case you want to decrease the number of threads
      • your main tool is to await asynchrous I/O
      • make sure you have an unbroken await chain from your async Action method down to the actual I/O
      • doing synchronous I/O or CPU intensive work on a thread (with Task.Run()) is a de-optimization.
    0 讨论(0)
  • 2020-11-21 05:18

    Below is code which reads excel file by opening dialog and then uses async and wait to run asynchronous the code which reads one by one line from excel and binds to grid

    namespace EmailBillingRates
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
                lblProcessing.Text = "";
            }
    
            private async void btnReadExcel_Click(object sender, EventArgs e)
            {
                string filename = OpenFileDialog();
    
                Microsoft.Office.Interop.Excel.Application xlApp = new Microsoft.Office.Interop.Excel.Application();
                Microsoft.Office.Interop.Excel.Workbook xlWorkbook = xlApp.Workbooks.Open(filename);
                Microsoft.Office.Interop.Excel._Worksheet xlWorksheet = xlWorkbook.Sheets[1];
                Microsoft.Office.Interop.Excel.Range xlRange = xlWorksheet.UsedRange;
                try
                {
                    Task<int> longRunningTask = BindGrid(xlRange);
                    int result = await longRunningTask;
    
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message.ToString());
                }
                finally
                {
                    //cleanup  
                   // GC.Collect();
                    //GC.WaitForPendingFinalizers();
    
                    //rule of thumb for releasing com objects:  
                    //  never use two dots, all COM objects must be referenced and released individually  
                    //  ex: [somthing].[something].[something] is bad  
    
                    //release com objects to fully kill excel process from running in the background  
                    Marshal.ReleaseComObject(xlRange);
                    Marshal.ReleaseComObject(xlWorksheet);
    
                    //close and release  
                    xlWorkbook.Close();
                    Marshal.ReleaseComObject(xlWorkbook);
    
                    //quit and release  
                    xlApp.Quit();
                    Marshal.ReleaseComObject(xlApp);
                }
    
            }
    
            private void btnSendEmail_Click(object sender, EventArgs e)
            {
    
            }
    
            private string OpenFileDialog()
            {
                string filename = "";
                OpenFileDialog fdlg = new OpenFileDialog();
                fdlg.Title = "Excel File Dialog";
                fdlg.InitialDirectory = @"c:\";
                fdlg.Filter = "All files (*.*)|*.*|All files (*.*)|*.*";
                fdlg.FilterIndex = 2;
                fdlg.RestoreDirectory = true;
                if (fdlg.ShowDialog() == DialogResult.OK)
                {
                    filename = fdlg.FileName;
                }
                return filename;
            }
    
            private async Task<int> BindGrid(Microsoft.Office.Interop.Excel.Range xlRange)
            {
                lblProcessing.Text = "Processing File.. Please wait";
                int rowCount = xlRange.Rows.Count;
                int colCount = xlRange.Columns.Count;
    
                // dt.Column = colCount;  
                dataGridView1.ColumnCount = colCount;
                dataGridView1.RowCount = rowCount;
    
                for (int i = 1; i <= rowCount; i++)
                {
                    for (int j = 1; j <= colCount; j++)
                    {
                        //write the value to the Grid  
                        if (xlRange.Cells[i, j] != null && xlRange.Cells[i, j].Value2 != null)
                        {
                             await Task.Delay(1);
                             dataGridView1.Rows[i - 1].Cells[j - 1].Value =  xlRange.Cells[i, j].Value2.ToString();
                        }
    
                    }
                }
                lblProcessing.Text = "";
                return 0;
            }
        }
    
        internal class async
        {
        }
    }
    
    0 讨论(0)
  • 2020-11-21 05:20

    On a higher level:

    1) Async keyword enables the await and that's all it does. Async keyword does not run the method in a separate thread. The beginning f async method runs synchronously until it hits await on a time-consuming task.

    2) You can await on a method that returns Task or Task of type T. You cannot await on async void method.

    3) The moment main thread encounters await on time-consuming task or when the actual work is started, the main thread returns to the caller of the current method.

    4) If the main thread sees await on a task that is still executing, it doesn't wait for it and returns to the caller of the current method. In this way, the application remains responsive.

    5) Await on processing task, will now execute on a separate thread from the thread pool.

    6) When this await task is completed, all the code below it will be executed by the separate thread

    Below is the sample code. Execute it and check the thread id

    using System;
    using System.Threading;
    using System.Threading.Tasks;
    
    namespace AsyncAwaitDemo
    {
        class Program
        {
            public static async void AsynchronousOperation()
            {
                Console.WriteLine("Inside AsynchronousOperation Before AsyncMethod, Thread Id: " + Thread.CurrentThread.ManagedThreadId);
                //Task<int> _task = AsyncMethod();
                int count = await AsyncMethod();
    
                Console.WriteLine("Inside AsynchronousOperation After AsyncMethod Before Await, Thread Id: " + Thread.CurrentThread.ManagedThreadId);
    
                //int count = await _task;
    
                Console.WriteLine("Inside AsynchronousOperation After AsyncMethod After Await Before DependentMethod, Thread Id: " + Thread.CurrentThread.ManagedThreadId);
    
                DependentMethod(count);
    
                Console.WriteLine("Inside AsynchronousOperation After AsyncMethod After Await After DependentMethod, Thread Id: " + Thread.CurrentThread.ManagedThreadId);
            }
    
            public static async Task<int> AsyncMethod()
            {
                Console.WriteLine("Inside AsyncMethod, Thread Id: " + Thread.CurrentThread.ManagedThreadId);
                int count = 0;
    
                await Task.Run(() =>
                {
                    Console.WriteLine("Executing a long running task which takes 10 seconds to complete, Thread Id: " + Thread.CurrentThread.ManagedThreadId);
                    Thread.Sleep(20000);
                    count = 10;
                });
    
                Console.WriteLine("Completed AsyncMethod, Thread Id: " + Thread.CurrentThread.ManagedThreadId);
    
                return count;
            }       
    
            public static void DependentMethod(int count)
            {
                Console.WriteLine("Inside DependentMethod, Thread Id: " + Thread.CurrentThread.ManagedThreadId + ". Total count is " + count);
            }
    
            static void Main(string[] args)
            {
                Console.WriteLine("Started Main method, Thread Id: " + Thread.CurrentThread.ManagedThreadId);
    
                AsynchronousOperation();
    
                Console.WriteLine("Completed Main method, Thread Id: " + Thread.CurrentThread.ManagedThreadId);
    
                Console.ReadKey();
            }
    
        }
    }
    
    0 讨论(0)
提交回复
热议问题