我一直在寻找以上两对之间的差异,但是没有找到任何清楚地解释它以及何时使用一个或另一个的文章。
那么SaveChanges()
和之间有什么区别SaveChangesAsync()
?
在Find()
和之间FindAsync()
?
在服务器端,当我们使用Async
方法时,我们还需要添加await
。因此,我认为它在服务器端不是异步的。
它仅有助于防止UI在客户端浏览器上阻塞吗?还是它们之间有什么优缺点?
Answers:
任何时候需要在远程服务器上执行操作时,程序都会生成请求,将其发送,然后等待响应。我将使用SaveChanges()
和SaveChangesAsync()
作为示例,但对Find()
和适用相同FindAsync()
。
假设您有一个myList
需要添加到数据库中的100多个项目的列表。要插入它,您的函数应如下所示:
using(var context = new MyEDM())
{
context.MyTable.AddRange(myList);
context.SaveChanges();
}
首先,您创建一个实例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");
}
这是很小的变化,但是对代码的效率和性能产生了深远的影响。那会怎样呢?代码的开头是相同的,您创建的实例MyEDM
并将其添加myList
到MyTable
。但是,当您调用时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");
}
我不知道为什么会有这样的功能,但是输出的内容说明了它是如何async await
工作的。首先,让我们回顾一下会发生什么。
执行进入MyCallingFunction
,Function Starting
然后Save Starting
写入控制台,然后SaveChangesAsync()
调用该函数。此时,执行返回MyCallingFunction
并进入for循环,最多写入1000次“ Continue to Execute”。当SaveChangesAsync()
完成后,执行返回SaveRecords
功能,写Save Complete
到控制台。一旦一切都SaveRecords
完成了,那么将在完成时继续MyCallingFunction
正确执行SaveChangesAsync()
。困惑?这是示例输出:
功能启动 保存开始 继续执行! 继续执行! 继续执行! 继续执行! 继续执行! .... 继续执行! 保存完成! 继续执行! 继续执行! 继续执行! .... 继续执行! 功能齐全!
或许:
功能启动 保存开始 继续执行! 继续执行! 保存完成! 继续执行! 继续执行! 继续执行! .... 继续执行! 功能齐全!
这就是它的美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());
}
在这里,你有四个不同的保存记录功能会在同一时间。与单个函数被串行调用相比,MyCallingFunction
使用它可以更快地完成。async await
SaveRecords
我尚未触及的一件事是await
关键字。这样做是阻止当前函数执行,直到Task
您等待的内容完成。因此,对于原始版本而言MyCallingFunction
,Function Complete
在SaveRecords
功能完成之前,该行不会被写入控制台。
长话短说,如果您可以选择使用async await
,则应该这样做,因为它将大大提高应用程序的性能。
await
但是,如果您使用它,即使在调用SaveChanges之后不需要执行任何其他操作,ASP也会说“啊哈,此线程返回以等待异步操作,这意味着我可以让此线程同时处理其他一些请求!” 这使您的应用水平扩展更好。
await
SaveChangesAsync
我剩下的解释将基于以下代码片段。
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
}
}
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;
}
备注:由于JobAsync
旋转的同步部分(绿色)比任务t
(红色)旋转的时间长,因此任务t
已在的点完成await t
。结果,延续(蓝色)在与绿色线程相同的线程上运行。Main
绿色的纺丝完成后,(白色)的同步部分将旋转。这就是为什么异步方法中的同步部分有问题的原因。
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;
}
备注:这种情况与第一种情况相反。JobAsync
自旋的同步部分(绿色)比任务t
(红色)短,则任务t
尚未在的位置完成await t
。结果,延续(蓝色)在与绿色线程不同的线程上运行。Main
绿色的纺丝完成后,(白色)的同步部分仍然旋转。
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;
}
备注:这种情况将解决以前情况下有关异步方法中的同步部分的问题。t
立即等待任务。结果,延续(蓝色)在与绿色线程不同的线程上运行。Main
(白色)的同步部分将立即平行于旋转JobAsync
。
如果要添加其他案例,请随时进行编辑。
此语句不正确:
在服务器端,当我们使用异步方法时,我们还需要添加await。
您不需要添加“ await”,await
它只是C#中的一个便捷关键字,使您可以在调用之后编写更多行代码,而其他行仅在Save操作完成后才执行。但是正如您所指出的,您可以简单地通过调用SaveChanges
而不是来实现SaveChangesAsync
。
但从根本上讲,异步调用远不止于此。这里的想法是,如果在“保存”操作进行期间还有其他工作(在服务器上)可以执行,则应使用SaveChangesAsync
。不要使用“等待”。只需调用SaveChangesAsync
,然后继续并行执行其他操作即可。这可能包括在Web应用程序中甚至在保存完成之前就将响应返回给客户端。但是,当然,您仍然希望检查保存的最终结果,以便万一失败,您可以将其传达给用户或以某种方式记录下来。