94

I have been searching for the differences between 2 pairs above but haven't found any articles explaining clearly about it as well as when to use one or another.

So what is the difference between SaveChanges() and SaveChangesAsync()?
And between Find() and FindAsync()?

On server side, when we use Async methods, we also need to add await. Thus, I don't think it is asynchronous on server side.

Does it only help to prevent the UI blocking on client side browser? Or are there any pros and cons between them?

Jacob Lambert
  • 6,465
  • 6
  • 21
  • 40
Hien Tran
  • 1,223
  • 1
  • 11
  • 17
  • 2
    async is much, *much* more than stopping the client UI thread from blocking in client applications. I'm sure there's an expert answer coming shortly. – jdphenix May 05 '15 at 01:27

3 Answers3

183

Any time that you need to do an action on a remote server, your program generates the request, sends it, then waits for a response. I will use SaveChanges() and SaveChangesAsync() as an example but the same applies to Find() and FindAsync().

Say you have a list myList of 100+ items that you need to add to your database. To insert that, your function would look something like so:

using(var context = new MyEDM())
{
    context.MyTable.AddRange(myList);
    context.SaveChanges();
}

First you create an instance of MyEDM, add the list myList to the table MyTable, then call SaveChanges() to persist the changes to the database. It works how you want, the records get committed, but your program cannot do anything else until the commit finishes. This can take a long time depending on what you are committing. If you are committing changes to the records, entity has to commit those one at a time (I once had a save take 2 minutes for updates)!

To solve this problem, you could do one of two things. The first is you can start up a new thread to handle the insert. While this will free up the calling thread to continue executing, you created a new thread that is just going to sit there and wait. There is no need for that overhead, and this is what the async await pattern solves.

For I/O opperations, await quickly becomes your best friend. Taking the code section from above, we can modify it to be:

using(var context = new MyEDM())
{
    Console.WriteLine("Save Starting");
    context.MyTable.AddRange(myList);
    await context.SaveChangesAsync();
    Console.WriteLine("Save Complete");
}

It is a very small change, but there are profound effects on the efficiency and performance of your code. So what happens? The begining of the code is the same, you create an instance of MyEDM and add your myList to MyTable. But when you call await context.SaveChangesAsync(), the execution of code returns to the calling function! So while you are waiting for all those records to commit, your code can continue to execute. Say the function that contained the above code had the signature of public async Task SaveRecords(List<MyTable> saveList), the calling function could look like this:

public async Task MyCallingFunction()
{
    Console.WriteLine("Function Starting");
    Task saveTask = SaveRecords(GenerateNewRecords());

    for(int i = 0; i < 1000; i++){
        Console.WriteLine("Continuing to execute!");
    }

    await saveTask;
    Console.Log("Function Complete");
}

Why you would have a function like this, I don't know, but what it outputs shows how async await works. First let's go over what happens.

Execution enters MyCallingFunction, Function Starting then Save Starting gets written to the console, then the function SaveChangesAsync() gets called. At this point, execution returns to MyCallingFunction and enters the for loop writing 'Continuing to Execute' up to 1000 times. When SaveChangesAsync() finishes, execution returns to the SaveRecordsfunction, writing Save Complete to the console. Once everything in SaveRecords completes, execution will continue in MyCallingFunction right were it was when SaveChangesAsync() finished. Confused? Here is an example output:

Function Starting
Save Starting
Continuing to execute!
Continuing to execute!
Continuing to execute!
Continuing to execute!
Continuing to execute!
....
Continuing to execute!
Save Complete!
Continuing to execute!
Continuing to execute!
Continuing to execute!
....
Continuing to execute!
Function Complete!

Or maybe:

Function Starting
Save Starting
Continuing to execute!
Continuing to execute!
Save Complete!
Continuing to execute!
Continuing to execute!
Continuing to execute!
....
Continuing to execute!
Function Complete!

That is the beauty of async await, your code can continue to run while you are waiting for something to finish. In reality, you would have a function more like this as your calling function:

public async Task MyCallingFunction()
{
    List<Task> myTasks = new List<Task>();
    myTasks.Add(SaveRecords(GenerateNewRecords()));
    myTasks.Add(SaveRecords2(GenerateNewRecords2()));
    myTasks.Add(SaveRecords3(GenerateNewRecords3()));
    myTasks.Add(SaveRecords4(GenerateNewRecords4()));

    await Task.WhenAll(myTasks.ToArray());
}

Here, you have four different save record functions going at the same time. MyCallingFunction will complete a lot faster using async await than if the individual SaveRecords functions were called in series.

The one thing that I have not touched on yet is the await keyword. What this does is stop the current function from executing until whatever Task you are awaiting completes. So in the case of the original MyCallingFunction, the line Function Complete will not be written to the console until the SaveRecords function finishes.

Long story short, if you have an option to use async await, you should as it will greatly increase the performance of your application.

Mr Anderson
  • 2,022
  • 9
  • 21
Jacob Lambert
  • 6,465
  • 6
  • 21
  • 40
  • 9
    99% of the time I still have to wait for values to be received from database before I can continue. Should I still be using async? Does async allow 100 people to connect to my website asynchronously? If I don't use async does that mean all 100 users have to wait in line 1 at a time? – MIKE Jul 10 '15 at 17:46
  • 6
    Worth noting: Spawning a new thread from the thread pool makes ASP a sad panda since you basically rob a thread from ASP (meaning that the thread can't handle other requests or do anything at all since it's stuck in a blocking call). If you use `await` however, even if YOU don't need to do anything else after the call to SaveChanges, ASP will say "aha, this thread returned awaiting an async operation, this means I can let this thread handle some other request in the meantime!" This makes your app scale horizontally much better. – sara Jan 13 '16 at 10:26
  • 3
    Actually I've benchmarked async to be slower. And have you ever seen how many threads are available in a typical ASP.Net server? It's like tens of thousands. So the odds of running out of threads to handle further requests is very unlikely and even if you did have enough traffic to saturate all those threads, is your server really powerful enough to not buckle in that case anyway? To make a claim that using async everywhere increases performance is totally wrong. It can in certain scenarios, but in most common situations it will in fact be slower. Benchmark and see. – user3766657 Feb 26 '16 at 18:28
  • @MIKE while a single user must wait for the database to return data to continue, your other users using your application doesn't. While IIS creates a thread for each request (actually its more complex than that), your awaiting thread can be used to handle other requests, this is important for scalability afaik. Imaging each request, instead of using 1 thread full time it uses many shorter threads that can be reused somewhere else (aka another requests). – Bart Calixto Nov 04 '16 at 00:15
  • Really nice explanation – mojoblanco Sep 11 '18 at 15:33
  • 1
    I'd like just to add that you **should always** `await` for `SaveChangesAsync` since EF does not support multiple saves at the same time. https://docs.microsoft.com/en-us/ef/core/saving/async Also, there's actually a great advantage on using these async methods. For example, you can keep receiving other requests in your webApi when saving data or doing lots of sutuff, or improve user experience not freezing the interface when you're in a desktop application. – tgarcia Mar 25 '19 at 22:46
  • My answer below illustrates some cases that support your nice explanation. – The Shortest Mustache Theorem Oct 09 '19 at 19:23
1

My remaining explanation will be based on the following code snippet.

using System;
using System.Threading;
using System.Threading.Tasks;
using static System.Console;

public static class Program
{
    const int N = 20;
    static readonly object obj = new object();
    static int counter;

    public static void Job(ConsoleColor color, int multiplier = 1)
    {
        for (long i = 0; i < N * multiplier; i++)
        {
            lock (obj)
            {
                counter++;
                ForegroundColor = color;
                Write($"{Thread.CurrentThread.ManagedThreadId}");
                if (counter % N == 0) WriteLine();
                ResetColor();
            }
            Thread.Sleep(N);
        }
    }

    static async Task JobAsync()
    {
       // intentionally removed
    }

    public static async Task Main()
    {
       // intentionally removed
    }
}

Case 1

static async Task JobAsync()
{
    Task t = Task.Run(() => Job(ConsoleColor.Red, 1));
    Job(ConsoleColor.Green, 2);
    await t;
    Job(ConsoleColor.Blue, 1);
}

public static async Task Main()
{
    Task t = JobAsync();
    Job(ConsoleColor.White, 1);
    await t;
}

enter image description here

Remarks: As the synchronous part (green) of JobAsync spins longer than the task t (red) then the task t is already completed at the point of await t. As a result, the continuation (blue) runs on the same thread as the green one. The synchronous part of Main (white) will spin after the green one is finished spinning. That is why the synchronous part in asynchronous method is problematic.

Case 2

static async Task JobAsync()
{
    Task t = Task.Run(() => Job(ConsoleColor.Red, 2));
    Job(ConsoleColor.Green, 1);
    await t;
    Job(ConsoleColor.Blue, 1);
}

public static async Task Main()
{
    Task t = JobAsync();
    Job(ConsoleColor.White, 1);
    await t;
}

enter image description here

Remarks: This case is opposite to the first case. The synchronous part (green) of JobAsync spins shorter than the task t (red) then the task t has not been completed at the point of await t. As a result, the continuation (blue) runs on the different thread as the green one. The synchronous part of Main (white) still spins after the green one is finished spinning.

Case 3

static async Task JobAsync()
{
    Task t = Task.Run(() => Job(ConsoleColor.Red, 1));
    await t;
    Job(ConsoleColor.Green, 1);
    Job(ConsoleColor.Blue, 1);
}

public static async Task Main()
{
    Task t = JobAsync();
    Job(ConsoleColor.White, 1);
    await t;
}

enter image description here

Remarks: This case will solve the problem in the previous cases about the synchronous part in asynchronous method. The task t is immediately awaited. As a result, the continuation (blue) runs on the different thread as the green one. The synchronous part of Main (white) will spin immediately parallel to JobAsync.

If you want to add other cases, feel free to edit.

-1

This statement is incorrect:

On server side, when we use Async methods, we also need to add await.

You do not need to add "await", await is merely a convenient keyword in C# that enables you to write more lines of code after the call, and those other lines will only get executed after the Save operation completes. But as you pointed out, you could accomplish that simply by calling SaveChanges instead of SaveChangesAsync.

But fundamentally, an async call is about much more than that. The idea here is that if there is other work you can do (on the server) while the Save operation is in progress, then you should use SaveChangesAsync. Do not use "await". Just call SaveChangesAsync, and then continue to do other stuff in parallel. This includes potentially, in a web app, returning a response to the client even before the Save has completed. But of course, you still will want to check the final result of the Save so that in case it fails, you can communicate that to your user, or log it somehow.

d219
  • 2,275
  • 5
  • 21
  • 29
Rajeev Goel
  • 1,217
  • 7
  • 8
  • 6
    You actually want to await these calls otherwise you might run queries and or save data concurrently using the same DbContext instance and DbContext is not thread safe. On top of that await makes it easy to handle exceptions. Without await you would have to store the task and check if it is faulted but without knowing when the task completed you wouldn't know when to check unless you use '.ContinueWith' which requires much more thought than await. – Pawel May 05 '15 at 02:35
  • 28
    This answer is deceptive, Calling an async method without await makes it a "fire and forget." The method goes off and will probably complete sometime, but you will never know when, and if it throws an exception you will never hear about it, You are unable to synchronize with its completion. This kind of potentially dangerous behavior should be chosen, not invoked with a simple (and incorrect) rule like "awaits on the client, no awaits on the server." – John Melville Jun 04 '15 at 03:27
  • 1
    This is a very useful piece of knowledge I had read in documentation, but hadn't really considered. So, you have the option to: 1. SaveChangesAsync() to "Fire and forget", like John Melville says... which is useful to me in some cases. 2. await SaveChangesAsync() to "Fire, return to the caller, and then execute some 'post-save' code after the save is completed. Very useful piece. Thank you. – Parrhesia Joe Oct 21 '15 at 04:01
  • This post is really wrong, you should always use `SaveChangesAsync` even when you do not need to fire and forget. Calling `SaveChanges` spins up new thread and wastes server resources. – Shadow Jan 27 '21 at 13:57