关于c#:Entity Framework SaveChanges()与SaveChangesAsync()和Find()与FindAsync()

Entity Framework SaveChanges() vs. SaveChangesAsync() and Find() vs. FindAsync()

我一直在寻找上面两对之间的差异,但是没有找到任何清楚地解释它以及何时使用一个或另一个的文章。

那么SaveChanges()SaveChangesAsync()有什么区别?
Find()FindAsync()之间?

在服务器端,当我们使用Async方法时,我们还需要添加await。 因此,我认为它在服务器端不是异步的。

它仅有助于防止UI在客户端浏览器上阻塞吗? 还是它们之间有什么优缺点?


任何时候需要在远程服务器上执行操作时,程序都会生成请求,将其发送,然后等待响应。我将使用SaveChanges()SaveChangesAsync()作为示例,但是对于Find()FindAsync()也是一样。

假设您有一个列表myList,其中包含要添加到数据库中的100多个项目。要插入它,您的函数应如下所示:

1
2
3
4
5
using(var context = new MyEDM())
{
    context.MyTable.AddRange(myList);
    context.SaveChanges();
}

首先,创建一个MyEDM实例,将列表myList添加到表MyTable中,然后调用SaveChanges()将更改保存到数据库中。它可以按您想要的方式工作,记录被提交,但是在提交完成之前,您的程序无法执行其他任何操作。这可能需要很长时间,具体取决于您要提交的内容。如果您要对记录进行更改,则实体必须一次提交一个记录(我曾经保存了2分钟以进行更新)!

要解决此问题,您可以执行以下两项操作之一。首先是您可以启动新线程来处理插入。尽管这将释放调用线程以继续执行,但您创建了一个新线程,该线程将坐在那里等待。不需要这些开销,这就是async await模式解决的问题。

对于I / O操作,await迅速成为您最好的朋友。从上面的代码部分中,我们可以将其修改为:

1
2
3
4
5
6
7
using(var context = new MyEDM())
{
    Console.WriteLine("Save Starting");
    context.MyTable.AddRange(myList);
    await context.SaveChangesAsync();
    Console.WriteLine("Save Complete");
}

这是很小的变化,但是对代码的效率和性能产生了深远的影响。那会怎样呢?代码的开头是相同的,创建MyEDM的实例并将myList添加到MyTable。但是,当您调用await context.SaveChangesAsync()时,代码的执行将返回到调用函数!因此,在等待所有这些记录提交时,您的代码可以继续执行。假设包含以上代码的函数的签名为public async Task SaveRecords(List saveList),则调用函数可能如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
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");
}

我不知道为什么会有这样的功能,但是输出的内容显示了async await的工作方式。首先,让我们回顾一下会发生什么。

执行进入MyCallingFunctionFunction Starting,然后将Save Starting写入控制台,然后调用函数SaveChangesAsync()。此时,执行返回到MyCallingFunction,并进入for循环,最多写入" Continue to Execute"(执行中)1000次。 SaveChangesAsync()完成后,执行返回SaveRecords函数,将Save Complete写入控制台。一旦SaveRecords中的所有内容完成,就将在SaveChangesAsync()完成时在MyCallingFunction中继续执行。困惑?这是示例输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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!

或许:

1
2
3
4
5
6
7
8
9
10
11
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!

这就是async await的优点,您可以在等待完成时继续运行代码。实际上,您将有一个类似于以下的函数作为调用函数:

1
2
3
4
5
6
7
8
9
10
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());
}

在这里,您同时具有四个不同的保存记录功能。使用async await完成MyCallingFunction的速度要比单独调用SaveRecords函数的速度快得多。

我尚未触及的一件事是await关键字。这样做是阻止当前函数执行,直到您等待的Task完成为止。因此,对于原始的MyCallingFunction,在SaveRecords函数完成之前,行Function Complete不会被写入控制台。

长话短说,如果您可以选择使用async await,则应该这样做,因为它将大大提高应用程序的性能。


我剩下的解释将基于以下代码片段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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
    }
}

情况1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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

注释:当JobAsync的同步部分(绿色)旋转的时间大于任务t(红色)的旋转时,则任务tawait t的点已经完成。结果,延续(蓝色)在与绿色线程相同的线程上运行。
绿色的旋转完成后,Main的同步部分(白色)将旋转。这就是为什么异步方法中的同步部分有问题的原因。

情况二

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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

备注:这种情况与第一种情况相反。 JobAsync的同步部分(绿色)旋转得比任务t(红色)短,则任务t尚未在await t点完成。结果,延续(蓝色)在与绿色线程不同的线程上运行。
绿色完成旋转后,Main的同步部分(白色)仍然旋转。

情况3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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

备注:这种情况将解决以前情况下有关异步方法中的同步部分的问题。立即等待任务t。结果,延续(蓝色)在与绿色线程不同的线程上运行。
Main的同步部分(白色)将立即平行于JobAsync旋转。

如果要添加其他案例,请随时进行编辑。


此语句不正确:

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

您不需要添加" await",await只是C#中的一个方便关键字,它使您可以在调用后编写更多行代码,而其他行仅在Save操作完成后才会执行。 但是正如您所指出的,您可以简单地通过调用SaveChanges而不是SaveChangesAsync来实现。

但从根本上讲,异步调用远不止于此。 这里的想法是,如果在"保存"操作正在进行时还可以(在服务器上)执行其他工作,则应使用SaveChangesAsync。 不要使用"等待"。 只需调用SaveChangesAsync,然后继续并行执行其他操作即可。 这可能包括在Web应用程序中甚至在保存完成之前就将响应返回给客户端。 但是,当然,您仍然希望检查保存的最终结果,以便万一失败,您可以将其传达给用户或以某种方式记录下来。