비동기 쿼리 및 저장

참고 항목

EF6 이상만 - 이 페이지에서 다루는 기능, API 등은 Entity Framework 6에 도입되었습니다. 이전 버전을 사용하는 경우 이 정보의 일부 또는 전체가 적용되지 않습니다.

EF6은 .NET 4.5에서 도입된 비동기 및 대기 키워드 사용하여 비동기 쿼리 및 저장에 대한 지원을 도입했습니다. 모든 애플리케이션이 비동기 기능을 활용하는 것은 아니지만 장기 실행, 네트워크 또는 I/O 바인딩 작업을 처리할 때 클라이언트 응답성 및 서버 확장성을 개선하는 데 사용할 수 있습니다.

비동기를 실제로 사용해야 하는 경우

이 연습의 목적은 비동기 프로그램 실행과 동기 프로그램 실행의 차이를 쉽게 관찰할 수 있는 방식으로 비동기 개념을 도입하는 것입니다. 이 연습은 비동기 프로그래밍이 이점을 제공하는 주요 시나리오를 설명하기 위한 것이 아닙니다.

비동기 프로그래밍은 주로 관리되는 스레드에서 컴퓨팅 시간이 필요하지 않은 작업을 기다리는 동안 현재 관리 스레드(.NET 코드를 실행하는 스레드)를 확보하여 다른 작업을 수행하는 데 중점을 줍니다. 예를 들어 데이터베이스 엔진이 쿼리를 처리하는 동안 .NET 코드로 수행할 작업은 없습니다.

클라이언트 애플리케이션(WinForms, WPF 등)에서 현재 스레드를 사용하여 비동기 작업이 수행되는 동안 UI 응답성을 유지할 수 있습니다. 서버 애플리케이션(ASP.NET 등)에서 스레드를 사용하여 들어오는 다른 요청을 처리할 수 있습니다. 그러면 메모리 사용량이 줄어들거나 서버의 처리량이 증가할 수 있습니다.

대부분의 애플리케이션에서 비동기 사용은 눈에 띄는 이점이 없으며 심지어 해로울 수도 있습니다. 테스트, 프로파일링 및 상식을 사용하여 커밋하기 전에 특정 시나리오에서 비동기의 영향을 측정합니다.

비동기에 대해 알아보는 몇 가지 추가 리소스는 다음과 같습니다.

모델 만들기

Code First 워크플로를 사용하여 모델을 만들고 데이터베이스를 생성하지만 비동기 기능은 EF 디자이너를 사용하여 만든 모델을 포함하여 모든 EF 모델에서 작동합니다.

  • 콘솔 애플리케이션 만들기 및 AsyncDemo 호출
  • EntityFramework NuGet 패키지 추가
    • 솔루션 탐색기에서 AsyncDemo 프로젝트를 마우스 오른쪽 단추로 클릭합니다.
    • NuGet 패키지 관리...를 선택합니다.
    • NuGet 패키지 관리 대화 상자에서 온라인 탭을 선택하고 EntityFramework 패키지를 선택합니다.
    • 설치를 클릭합니다.
  • 다음 구현을 사용하여 Model.cs 클래스를 추가합니다.
    using System.Collections.Generic;
    using System.Data.Entity;

    namespace AsyncDemo
    {
        public class BloggingContext : DbContext
        {
            public DbSet<Blog> Blogs { get; set; }
            public DbSet<Post> Posts { get; set; }
        }

        public class Blog
        {
            public int BlogId { get; set; }
            public string Name { get; set; }

            public virtual List<Post> Posts { get; set; }
        }

        public class Post
        {
            public int PostId { get; set; }
            public string Title { get; set; }
            public string Content { get; set; }

            public int BlogId { get; set; }
            public virtual Blog Blog { get; set; }
        }
    }

 

동기 프로그램 만들기

이제 EF 모델이 있으므로 일부 데이터 액세스를 수행하는 데 사용하는 코드를 작성해 보겠습니다.

  • Program.cs의 내용을 다음 코드로 바꿉니다.
    using System;
    using System.Linq;

    namespace AsyncDemo
    {
        class Program
        {
            static void Main(string[] args)
            {
                PerformDatabaseOperations();

                Console.WriteLine("Quote of the day");
                Console.WriteLine(" Don't worry about the world coming to an end today... ");
                Console.WriteLine(" It's already tomorrow in Australia.");

                Console.WriteLine();
                Console.WriteLine("Press any key to exit...");
                Console.ReadKey();
            }

            public static void PerformDatabaseOperations()
            {
                using (var db = new BloggingContext())
                {
                    // Create a new blog and save it
                    db.Blogs.Add(new Blog
                    {
                        Name = "Test Blog #" + (db.Blogs.Count() + 1)
                    });
                    Console.WriteLine("Calling SaveChanges.");
                    db.SaveChanges();
                    Console.WriteLine("SaveChanges completed.");

                    // Query for all blogs ordered by name
                    Console.WriteLine("Executing query.");
                    var blogs = (from b in db.Blogs
                                orderby b.Name
                                select b).ToList();

                    // Write all blogs out to Console
                    Console.WriteLine("Query completed with following results:");
                    foreach (var blog in blogs)
                    {
                        Console.WriteLine(" " + blog.Name);
                    }
                }
            }
        }
    }

이 코드는 새 블로그를 데이터베이스에 저장한 다음 데이터베이스에서 모든 블로그를 검색하여 콘솔에 출력하는 PerformDatabaseOperations 메서드를 호출합니다. 이후 프로그램은 콘솔에 하루의 견적을 씁니다.

코드는 동기적이므로 프로그램을 실행할 때 다음 실행 흐름을 관찰할 수 있습니다.

  1. SaveChanges는 새 블로그를 데이터베이스에 푸시하기 시작합니다.
  2. SaveChanges를 완료합니다.
  3. 모든 블로그에 대한 쿼리가 데이터베이스로 전송됩니다.
  4. 쿼리가 반환되고 결과가 콘솔에 기록됩니다.
  5. 오늘의 견적은 콘솔에 기록됩니다.

Sync Output 

 

비동기화

이제 프로그램을 가동하고 실행했으므로 새 비동기 및 대기 키워드를 사용할 수 있습니다. Program.cs를 다음과 같은 변경 내용으로 변경했습니다.

  1. 줄 2: System.Data.Entity 네임스페이스에 대한 using 문을 통해 EF 비동기 확장 메서드에 액세스할 수 있습니다.
  2. 줄 4: System.Threading.Tasks 네임스페이스에 대한 using 문을 통해 Task 형식을 사용할 수 있습니다.
  3. 줄 12 및 18: PerformSomeDatabaseOperations(줄 12)의 진행률을 모니터링하는 작업으로 캡처한 다음 프로그램에 대한 모든 작업이 완료되면 이 작업이 완료되도록 프로그램 실행을 차단합니다(줄 18).
  4. 줄 25: async로 표시되고 Task를 반환하도록 PerformSomeDatabaseOperations를 업데이트 했습니다.
  5. 줄 35: 이제 SaveChanges의 비동기 버전을 호출하고 완료되기를 기다리고 있습니다.
  6. 줄 42: 이제 ToList의 비동기 버전을 호출하고 결과를 기다리고 있습니다.

System.Data.Entity 네임스페이스에서 사용 가능한 확장 메서드의 포괄적인 목록은 QueryableExtensions 클래스를 참조하세요. 또한 using 문에 using System.Data.Entity를 추가해야 합니다.

    using System;
    using System.Data.Entity;
    using System.Linq;
    using System.Threading.Tasks;

    namespace AsyncDemo
    {
        class Program
        {
            static void Main(string[] args)
            {
                var task = PerformDatabaseOperations();

                Console.WriteLine("Quote of the day");
                Console.WriteLine(" Don't worry about the world coming to an end today... ");
                Console.WriteLine(" It's already tomorrow in Australia.");

                task.Wait();

                Console.WriteLine();
                Console.WriteLine("Press any key to exit...");
                Console.ReadKey();
            }

            public static async Task PerformDatabaseOperations()
            {
                using (var db = new BloggingContext())
                {
                    // Create a new blog and save it
                    db.Blogs.Add(new Blog
                    {
                        Name = "Test Blog #" + (db.Blogs.Count() + 1)
                    });
                    Console.WriteLine("Calling SaveChanges.");
                    await db.SaveChangesAsync();
                    Console.WriteLine("SaveChanges completed.");

                    // Query for all blogs ordered by name
                    Console.WriteLine("Executing query.");
                    var blogs = await (from b in db.Blogs
                                orderby b.Name
                                select b).ToListAsync();

                    // Write all blogs out to Console
                    Console.WriteLine("Query completed with following results:");
                    foreach (var blog in blogs)
                    {
                        Console.WriteLine(" - " + blog.Name);
                    }
                }
            }
        }
    }

이제 코드가 비동기이므로 프로그램을 실행할 때 다른 실행 흐름을 관찰할 수 있습니다.

  1. SaveChanges는 새 블로그를 데이터베이스에 푸시하기 시작합니다.
    명령이 데이터베이스로 전송되면 현재 관리되는 스레드에서 더 이상 컴퓨팅 시간이 필요하지 않습니다. PerformDatabaseOperations 메서드는 실행을 완료하지 않았더라도 반환하고 Main 메서드의 프로그램 흐름은 계속됩니다.
  2. 오늘의 견적은 콘솔에 기록됩니다.
    Main 메서드에서 수행할 작업이 더 이상 없으므로 데이터베이스 작업이 완료될 때까지 Wait 호출에서 관리되는 스레드가 차단됩니다. 완료되면 PerformDatabaseOperations의 나머지가 실행됩니다.
  3. SaveChanges를 완료합니다.
  4. 모든 블로그에 대한 쿼리가 데이터베이스로 전송됩니다.
    다시 말하지만, 쿼리가 데이터베이스에서 처리되는 동안 관리되는 스레드는 다른 작업을 자유롭게 수행할 수 있습니다. 다른 모든 실행이 완료되었으므로 스레드는 대기 호출에서 중지됩니다.
  5. 쿼리가 반환되고 결과가 콘솔에 기록됩니다.

Async Output 

 

테이크 아웃

이제 EF의 비동기 메서드를 사용하는 것이 얼마나 쉬운지 확인했습니다. 간단한 콘솔 앱에서는 비동기의 장점이 명확하지 않을 수 있지만, 장기 실행 또는 네트워크 바인딩 작업이 애플리케이션을 차단하거나 많은 수의 스레드가 메모리 공간을 증가시킬 수 있는 상황에서도 이와 동일한 전략을 적용할 수 있습니다.