实体框架SaveChanges()与SaveChangesAsync()和Find()与FindAsync()

Hie*_*ran 75 c# entity-framework async-await

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

那么SaveChanges()和之间的区别是什么SaveChangesAsync()
之间Find()FindAsync()

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

它只会阻止客户端浏览器上的UI阻止吗?或者它们之间有任何利弊吗?

Jac*_*ert 149

只要您需要在远程服务器上执行操作,程序就会生成请求,发送请求,然后等待响应.我将使用SaveChanges()SaveChangesAsync()作为一个例子,但同样适用于Find()FindAsync().

假设您有一个myList需要添加到数据库中的100多个项目的列表.要插入它,你的函数看起来像这样:

using(var context = new MyEDM())
{
    context.MyTable.AddRange(myList);
    context.SaveChanges();
}
Run Code Online (Sandbox Code Playgroud)

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

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

对于I/O操作,await很快就会成为您最好的朋友.从上面的代码部分,我们可以将其修改为:

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

这是一个非常小的变化,但会对代码的效率和性能产生深远的影响.那会发生什么?代码的开头是相同的,你创建一个实例MyEDM并添加你myListMyTable.但是当你调用时await context.SaveChangesAsync(),代码的执行会返回到调用函数!因此,当您等待所有这些记录提交时,您的代码可以继续执行.说包含上面代码的函数有签名public async Task SaveRecords(List<MyTable> saveList),调用函数可能如下所示:

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");
}
Run Code Online (Sandbox Code Playgroud)

为什么你会有这样的功能,我不知道,但它输出的内容显示了它是如何async await工作的.首先让我们回顾一下发生的事情.

执行进入MyCallingFunction,Function Starting然后Save Starting写入控制台,然后SaveChangesAsync()调用该函数.此时,执行返回MyCallingFunction并进入for循环写入'Continuing to Execute'最多1000次.当SaveChangesAsync()完成后,执行返回SaveRecords功能,写Save Complete到控制台.一旦一切都SaveRecords完成,将继续执行在MyCallingFunction右分别是时候SaveChangesAsync()结束了.困惑?这是一个示例输出:

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!

或者可能:

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,你的代码可以在你等待完成的时候继续运行.实际上,你将拥有一个更像这样的函数作为你的调用函数:

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());
}
Run Code Online (Sandbox Code Playgroud)

在这里,你有四个不同的保存记录功能会在同一时间.与单个函数串行调用相比,MyCallingFunction使用速度更快.async awaitSaveRecords

我还没有涉及的一件事是await关键字.这样做是为了阻止当前函数执行,直到Task等待完成为止.因此,对于原始文件MyCallingFunction,Function CompleteSaveRecords函数完成之前,不会将该行写入控制台.

简而言之,如果您可以选择使用async await,那么您应该会大大提高应用程序的性能.

  • 在我可以继续之前,99%的时间我仍然需要等待从数据库接收值.我还应该使用异步吗?异步允许100个人异步连接到我的网站吗?如果我不使用异步,这意味着所有100个用户必须一次排队1? (5认同)
  • 值得注意的是:从线程池中产生一个新线程会使ASP变成悲伤的熊猫,因为您基本上是从ASP抢劫了一个线程(这意味着该线程无法处理其他请求,或者因为阻塞在阻塞调用中而无法执行任何操作)。但是,如果使用`await`,即使调用SaveChanges之后不需要执行任何其他操作,ASP也会说“啊哈,该线程返回以等待异步操作,这意味着我可以让该线程处理其他请求同时!” 这使您的应用水平扩展更好。 (3认同)
  • 实际上,我已经将异步基准测试的速度降低了。您是否曾见过典型的ASP.Net服务器中有多少个线程可用?就像成千上万。因此,用尽线程来处理更多请求的可能性很小,即使您确实有足够的流量来饱和所有这些线程,您的服务器是否真的足够强大以至于在这种情况下也不会崩溃?声称在各处使用异步都会提高性能是完全错误的。在某些情况下可以这样做,但实际上在大多数情况下它会更慢。进行基准测试。 (3认同)
  • @MIKE,虽然单个用户必须等待数据库返回数据才能继续,但使用您的应用程序的其他用户则不需要。虽然 IIS 为每个请求创建一个线程(实际上比这更复杂),但您的等待线程可用于处理其他请求,据我所知,这对于可伸缩性很重要。映像每个请求,而不是全职使用 1 个线程,而是使用许多较短的线程,这些线程可以在其他地方重用(也称为另一个请求)。 (2认同)
  • 我想补充一点,您**应该始终**“等待”“SaveChangesAsync”,因为 EF 不支持同时进行多个保存。https://learn.microsoft.com/en-us/ef/core/ saving/async 另外,使用这些异步方法实际上有很大的优势。例如,您可以在保存数据或执行大量操作时继续在 webApi 中接收其他请求,或者在使用桌面应用程序时改善用户体验而不冻结界面。 (2认同)

xpo*_*ort 5

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

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
    }
}
Run Code Online (Sandbox Code Playgroud)

情况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;
}
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

备注:由于 的同步部分(绿色)JobAsync自旋时间比任务t(红色)长,因此任务t在 点已经完成await t。因此,延续(蓝色)与绿色线程在同一线程上运行。(白色)的同步部分Main会在绿色部分旋转完毕后旋转。这就是为什么异步方法中的同步部分是有问题的。

案例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;
}
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

备注:本例与第一种情况相反。的同步部分(绿色)JobAsync自旋时间短于任务t(红色),则任务t在 点尚未完成await t。因此,延续(蓝色)与绿色线程在不同的线程上运行。(白色)的同步部分在Main绿色部分旋转完毕后仍在旋转。

案例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;
}
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

备注:本案例将解决前面案例中异步方法中同步部分的问题。t立即等待任务。因此,延续(蓝色)与绿色线程在不同的线程上运行。(白色)的同步部分Main将立即平行于 旋转JobAsync

如果您想添加其他案例,请随时编辑。