Tenho pesquisado as diferenças entre os 2 pares acima, mas não encontrei nenhum artigo explicando claramente sobre isso, bem como quando usar um ou outro.
Então, qual é a diferença entre SaveChanges()
e SaveChangesAsync()
?
E entre Find()
e FindAsync()
?
No lado do servidor, quando usamos Async
métodos, também precisamos adicionar await
. Portanto, não acho que seja assíncrono no lado do servidor.
Isso só ajuda a evitar o bloqueio da IU no navegador do lado do cliente? Ou existem prós e contras entre eles?
c#
entity-framework
async-await
Hien Tran
fonte
fonte
Respostas:
Sempre que você precisar realizar uma ação em um servidor remoto, seu programa gera a solicitação, a envia e espera por uma resposta. Vou usar
SaveChanges()
eSaveChangesAsync()
como exemplo, mas o mesmo se aplica aFind()
eFindAsync()
.Digamos que você tenha uma lista
myList
de mais de 100 itens que precise adicionar ao seu banco de dados. Para inserir isso, sua função seria mais ou menos assim:using(var context = new MyEDM()) { context.MyTable.AddRange(myList); context.SaveChanges(); }
Primeiro você cria uma instância de
MyEDM
, adiciona a listamyList
à tabela eMyTable
, em seguida, chamaSaveChanges()
para persistir as alterações no banco de dados. Funciona como você deseja, os registros são confirmados, mas seu programa não pode fazer mais nada até que a confirmação seja concluída. Isso pode levar muito tempo, dependendo do que você está enviando. Se você está enviando alterações para os registros, a entidade tem que confirmá-las, uma de cada vez (uma vez, um salvamento levou 2 minutos para atualizações)!Para resolver esse problema, você pode fazer uma das duas coisas. A primeira é que você pode iniciar uma nova rosca para lidar com a inserção. Embora isso libere o encadeamento de chamada para continuar em execução, você criou um novo encadeamento que vai ficar parado e esperar. Não há necessidade dessa sobrecarga, e é isso que o
async await
padrão resolve.Para operações de I / O,
await
rapidamente se torna seu melhor amigo. Pegando a seção de código acima, podemos modificá-la para ser:using(var context = new MyEDM()) { Console.WriteLine("Save Starting"); context.MyTable.AddRange(myList); await context.SaveChangesAsync(); Console.WriteLine("Save Complete"); }
É uma mudança muito pequena, mas há efeitos profundos na eficiência e no desempenho do seu código. Então o que acontece? O início do código é o mesmo, você cria uma instância de
MyEDM
e adiciona seumyList
aMyTable
. Mas quando você chamaawait context.SaveChangesAsync()
, a execução do código retorna para a função de chamada! Portanto, enquanto você espera a confirmação de todos esses registros, seu código pode continuar a ser executado. Digamos que a função que continha o código acima tivesse a assinatura depublic async Task SaveRecords(List<MyTable> saveList)
, a função de chamada poderia ter a seguinte aparência: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"); }
Por que você teria uma função como esta, eu não sei, mas o que sai mostra como
async await
funciona. Primeiro, vamos ver o que acontece.Execução entra
MyCallingFunction
,Function Starting
em seguida,Save Starting
é escrita para o console, em seguida, a funçãoSaveChangesAsync()
é chamada. Neste ponto, a execução retornaMyCallingFunction
e entra no loop for escrevendo 'Continuing to Execute' até 1000 vezes. AoSaveChangesAsync()
terminar, a execução retorna àSaveRecords
função, gravandoSave Complete
no console. Depois que tudo estiverSaveRecords
concluído, a execução continuará da maneiraMyCallingFunction
correta onde estava antesSaveChangesAsync()
. Confuso? Aqui está um exemplo de saída:Ou talvez:
Essa é a beleza de
async await
, seu código pode continuar a ser executado enquanto você espera que algo termine. Na realidade, você teria uma função mais parecida com esta como sua função de chamada: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()); }
Aqui, você tem quatro funções diferentes de salvar gravação em execução ao mesmo tempo .
MyCallingFunction
será concluído muito mais rápido usando doasync await
que se asSaveRecords
funções individuais fossem chamadas em série.A única coisa que ainda não mencionei é a
await
palavra - chave. O que isso faz é parar a execução da função atual até que tudoTask
o que você está esperando seja concluído. Portanto, no caso do originalMyCallingFunction
, a linhaFunction Complete
não será gravada no console até que aSaveRecords
função seja concluída.Resumindo, se você tem uma opção de usar
async await
, você deve, pois isso aumentará muito o desempenho do seu aplicativo.fonte
await
, no entanto, mesmo que VOCÊ não precise fazer mais nada após a chamada para SaveChanges, o ASP dirá "aha, este thread retornou aguardando uma operação assíncrona, isso significa que posso deixar este thread lidar com alguma outra solicitação nesse ínterim ! " Isso faz com que seu aplicativo seja dimensionado horizontalmente muito melhor.await
paraSaveChangesAsync
desde EF não suporta múltiplos salva ao mesmo tempo. docs.microsoft.com/en-us/ef/core/saving/async Além disso, há uma grande vantagem em usar esses métodos assíncronos. Por exemplo, você pode continuar recebendo outras solicitações em seu webApi ao salvar dados ou fazer muitos sutuff, ou melhorar a experiência do usuário sem congelar a interface quando estiver em um aplicativo de desktop.Minha explicação restante será baseada no seguinte trecho de código.
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 } }
Caso 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; }
Observações: Como a parte síncrona (verde) de
JobAsync
gira mais do que a tarefat
(vermelho), a tarefat
já está concluída no ponto deawait t
. Como resultado, a continuação (azul) segue no mesmo fio que a verde. A parte síncrona deMain
(branco) girará depois que a parte verde terminar de girar. É por isso que a parte síncrona no método assíncrono é problemática.Caso 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; }
Observações: Este caso é oposto ao primeiro caso. A parte síncrona (verde) dos
JobAsync
spins mais curtos do que a tarefat
(vermelho), então a tarefat
não foi concluída no ponto deawait t
. Como resultado, a continuação (azul) é executada na linha diferente da verde. A parte síncrona deMain
(branco) ainda gira depois que a parte verde termina de girar.Caso 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; }
Observações: Este caso resolverá o problema dos casos anteriores sobre a parte síncrona no método assíncrono. A tarefa
t
é aguardada imediatamente. Como resultado, a continuação (azul) é executada na linha diferente da verde. A parte síncrona deMain
(branco) girará imediatamente em paralelo aJobAsync
.Se você quiser adicionar outros casos, fique à vontade para editar.
fonte
Esta afirmação está incorreta:
Você não precisa adicionar "await",
await
é apenas uma palavra-chave conveniente em C # que permite escrever mais linhas de código após a chamada, e essas outras linhas só serão executadas após a conclusão da operação Salvar. Mas, como você indicou, você pode fazer isso simplesmente ligando emSaveChanges
vez deSaveChangesAsync
.Mas, fundamentalmente, uma chamada assíncrona envolve muito mais do que isso. A ideia aqui é que se houver outro trabalho que você possa fazer (no servidor) enquanto a operação Salvar estiver em andamento, você deve usar
SaveChangesAsync
. Não use "esperar". Basta ligarSaveChangesAsync
e continuar a fazer outras coisas em paralelo. Isso inclui potencialmente, em um aplicativo da web, retornar uma resposta ao cliente antes mesmo que o salvamento seja concluído. Mas é claro, você ainda vai querer verificar o resultado final do Salvar para que, caso ele falhe, você possa comunicar isso ao seu usuário, ou registrá-lo de alguma forma.fonte