Entity Framework SaveChanges () 대 SaveChangesAsync () 및 Find () 대 FindAsync ()


89

나는 위의 두 쌍의 차이점을 찾고 있었지만 그것에 대해 명확하게 설명하는 기사와 둘 중 하나를 사용해야 할 때를 찾지 못했습니다.

그래서 차이 무엇 SaveChanges()SaveChangesAsync()?
그리고 사이 Find()FindAsync()?

서버 측에서 Async메서드 를 사용할 때 await. 따라서 서버 측에서는 비동기 적이라고 생각하지 않습니다.

클라이언트 측 브라우저에서 UI 차단을 방지하는 데만 도움이됩니까? 아니면 그들 사이에 장단점이 있습니까?


2
비동기는 많이 훨씬 더 클라이언트 응용 프로그램에서 차단하는 클라이언트 UI 스레드를 중지하는 것보다. 곧 전문가의 답변이 올 것이라고 확신합니다.
jdphenix

답변:


176

원격 서버에서 작업을 수행해야 할 때마다 프로그램은 요청을 생성하고 전송 한 다음 응답을 기다립니다. 내가 사용 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최대 1000 회까지 'Continuing to Execute'를 쓰는 for 루프로 돌아가서 들어갑니다. 때 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());
}

여기에는 4 개의 서로 다른 기록 저장 기능 이 동시에 있습니다 . 개별 함수가 직렬로 호출되는 경우보다 MyCallingFunction훨씬 빠르게 완료됩니다 .async awaitSaveRecords

제가 아직 다루지 않은 것은 await키워드입니다. 이것이하는 일은 Task당신이 기다리고있는 모든 것이 완료 될 때까지 현재 함수의 실행을 중지하는 것 입니다. 따라서 원래의 경우 함수가 완료 될 때까지 MyCallingFunctionFunction Complete이 콘솔에 기록되지 않습니다 SaveRecords.

간단히 말해서를 사용할 수있는 옵션이 있다면 async await애플리케이션의 성능을 크게 향상시켜야합니다.


7
99 %의 시간 동안 계속하려면 데이터베이스에서 값을받을 때까지 기다려야합니다. 여전히 비동기를 사용해야합니까? 비동기로 100 명이 내 웹 사이트에 비동기 적으로 연결할 수 있나요? 비동기를 사용하지 않는다면 100 명의 사용자 모두가 한 번에 라인 1에서 기다려야한다는 의미입니까?
MIKE

6
주목할만한 점 : 스레드 풀에서 새 스레드를 생성하면 기본적으로 ASP에서 스레드를 빼앗기 때문에 ASP가 슬픈 팬더가됩니다 (즉, 스레드가 차단 호출에 갇혀 있기 때문에 스레드가 다른 요청을 처리하거나 아무것도 할 수 없음을 의미 함). await그러나 사용 하면 SaveChanges를 호출 한 후 다른 작업을 수행 할 필요가 없더라도 ASP는 "아하,이 스레드가 비동기 작업을 기다리는 동안 반환되었습니다.이 스레드가 그 동안 다른 요청을 처리하도록 할 수 있습니다. ! " 이렇게하면 앱의 수평 확장이 훨씬 향상됩니다.
sara jan

3
사실 저는 비동기를 더 느리게 벤치마킹했습니다. 일반적인 ASP.Net 서버에서 사용 가능한 스레드 수를 본 적이 있습니까? 그것은 수만과 같습니다. 따라서 추가 요청을 처리하기 위해 스레드가 부족할 가능성은 거의 없으며 이러한 모든 스레드를 포화시킬 충분한 트래픽이 있더라도 서버가이 경우에 버클 링하지 않을만큼 강력합니까? 어디서나 비동기를 사용하면 성능이 향상된다는 주장은 완전히 잘못된 것입니다. 특정 시나리오에서는 가능하지만 대부분의 일반적인 상황에서는 실제로 더 느립니다. 벤치마킹하고보십시오.
user3766657

@MIKE는 단일 사용자가 데이터베이스가 데이터를 계속 반환 할 때까지 기다려야하지만 애플리케이션을 사용하는 다른 사용자는 그렇지 않습니다. IIS는 각 요청에 대해 스레드를 생성하지만 (실제로는 그보다 더 복잡함) 대기중인 스레드를 사용하여 다른 요청을 처리 할 수 ​​있습니다. 이는 확장 성 측면에서 중요합니다. 각 요청을 이미징하는 대신 1 개의 스레드를 풀 타임으로 사용하는 대신 다른 곳에서 재사용 할 수있는 더 짧은 스레드 (다른 요청이라고도 함)를 많이 사용합니다.
Bart Calixto 2016

1
난 그냥 당신이 것을 추가 할 항상 있어야 await 하기위한 SaveChangesAsyncEF 여러 동시에 저장을 지원하지 않기 때문에. docs.microsoft.com/en-us/ef/core/saving/async 또한 이러한 비동기 메서드를 사용하면 실제로 큰 이점이 있습니다. 예를 들어 데이터를 저장하거나 많은 작업을 수행 할 때 webApi에서 다른 요청을 계속 수신하거나 데스크톱 애플리케이션을 사용할 때 인터페이스가 고정되지 않는 사용자 경험을 개선 할 수 있습니다.
tgarcia

1

나머지 설명은 다음 코드 스 니펫을 기반으로합니다.

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

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(흰색) 의 동기 부분은 녹색 부분 이 회전을 마치면 회전합니다. 이것이 비동기 방식의 동기 부분이 문제가되는 이유입니다.

사례 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;
}

여기에 이미지 설명 입력

비고 :이 경우는 첫 번째 경우와 반대입니다. 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;
}

여기에 이미지 설명 입력

비고 :이 경우 비동기 메서드의 동기 부분에 대한 이전 사례의 문제가 해결됩니다. 작업 t이 즉시 대기합니다. 결과적으로 연속 (파란색)은 녹색과 다른 스레드에서 실행됩니다. Main(흰색) 의 동기 부분 은와 즉시 평행하게 회전합니다 JobAsync.

다른 케이스를 추가하고 싶다면 자유롭게 편집하세요.


-1

이 문장은 올바르지 않습니다.

서버 측에서는 Async 메서드를 사용할 때 await도 추가해야합니다.

"await"를 추가 할 필요가 없습니다 await. C #에서는 호출 후 더 많은 코드 줄을 작성할 수있는 편리한 키워드 일 뿐이며 다른 줄은 저장 작업이 완료된 후에 만 ​​실행됩니다. 당신이 지적했듯이, 당신은 호출하여 단순히을 수행 할 수있는 SaveChanges대신에 SaveChangesAsync.

그러나 근본적으로 비동기 호출은 그 이상입니다. 여기서 아이디어는 저장 작업이 진행되는 동안 서버에서 수행 할 수있는 다른 작업이있는 경우을 사용해야한다는 것 SaveChangesAsync입니다. "await"를 사용하지 마십시오. 을 (를) 호출 SaveChangesAsync한 다음 계속해서 다른 작업을 병렬로 수행하십시오. 여기에는 잠재적으로 웹 앱에서 저장이 완료되기 전에도 클라이언트에 응답을 반환하는 것이 포함됩니다. 그러나 물론 저장의 최종 결과를 확인하여 실패 할 경우이를 사용자에게 알리거나 어떻게 든 기록 할 수 있습니다.


6
실제로 이러한 호출을 기다리고 싶지 않으면 쿼리를 실행하거나 동일한 DbContext 인스턴스를 사용하여 동시에 데이터를 저장할 수 있으며 DbContext는 스레드로부터 안전하지 않습니다. 그 외에도 await를 사용하면 예외를 쉽게 처리 할 수 ​​있습니다. await가 없으면 작업을 저장하고 오류가 있는지 확인해야하지만 작업이 언제 완료되었는지 알지 못하면 대기보다 훨씬 더 많은 생각이 필요한 '.ContinueWith'를 사용하지 않는 한 언제 확인해야할지 알 수 없습니다.
Pawel

25
이 대답은 기만적입니다. await없이 비동기 메서드를 호출하면 "불타고 잊어 버립니다". 메소드가 작동하지 않고 언젠가 완료 될 수 있지만 언제 완료되는지 알 수 없으며 예외가 발생하면 이에 대해 듣지 못할 것입니다. 완료와 동기화 할 수 없습니다. 이러한 종류의 잠재적으로 위험한 동작은 "클라이언트에서 깨어나고 서버에서 대기하지 않음"과 같은 단순하고 잘못된 규칙으로 호출하지 말고 선택해야합니다.
John Melville 2015 년

1
이것은 내가 문서에서 읽었지만 실제로 고려하지 않은 매우 유용한 지식입니다. 따라서 다음과 같은 옵션이 있습니다. 1. John Melville이 말한 것처럼 SaveChangesAsync ()를 "Fire and forget"으로 변경합니다. 이것은 어떤 경우에 저에게 유용합니다. 2. await SaveChangesAsync () to "Fire, 호출자에게 돌아가서 저장이 완료된 후 'post-save'코드를 실행합니다. 매우 유용한 부분입니다. 감사합니다.
Parrhesia Joe
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.