EF core는 동일한 DbContext 인스턴스에서 실행되는 여러 병렬 작업을 지원하지 않는다.
비동기 쿼리의 병렬 실행과 여러 스레드에서의 명시적 동시 사용도 안된다.
따라서 항상 await 비동기 호출을 즉시 수행하거나 병렬로 실행되는 작업에 대해서 별도의 DbContext 인스턴스를 사용한다.
EF core가 DbContext 인스턴스를 동시에 사용하려는 시도를 감지하면 다음과 같은 메시지가 포함된 InvalidOperationException이 표시된다.
( 내가 선거 결과 전송 프로그램에서 겪었던 문제이다... )
예외 내용은 이렇다.
이전 작업이 완료되기 전에 이 컨텍스트에서 두번째 작업이 시작되었습니다. 이 문제는 일반적으로 동일한 DbContext 인스턴스를 사용하는 다른 스레드에 의해서 발생하지만 인스턴스 멤버는 스레드로부터 안전하지 않을 수 있습니다.
동시 엑세스가 감지되지 않는 경우에는 정의되지 않은 동작, 어플리케이션 충돌 및 데이터 손상이 발생할 수 있다. 실수로 동일한 DbContext 인스턴스에서 동시 액세스를 유발할 수 있는 일반적인 오류가 있습니다.
DbContext는 내부적으로 여러 상태정보와 캐시를 관리한다고 했는데 이러한 정보들이 있는 DbContext를 여러 스레드가 동시에 건들이면(변경하면), 예측 불가능한 결과, 데이터 손상, 어플리케이션 충돌 등을 초래할 수 있다. 동시성 문제가 일어난다는 것이다.
그럼 동시 액세스를 유발할 수 있는 일반적인 오류를 보자.
1. 비동기 메서드를 동기적으로 실행한다.
예를 들어서 ToListAsync, SaveChangesAsync
Ef Core의 비동기메서드를 await 없이 호출하고 결과를 직접 사용하는 경우에 현재 스레드가 블로킹 될 수 있고, 이는 다른 스레드에서 같은 DbContext 인스턴스에 접근하도록 유도할 수 있다.
한번 살펴볼까. 비동기메서드는 기본적으로 Task를 반환한다. await은 이 태스크가 끝날때까지 호출스레드를 블로킹하고, 다른 스레드에서 해당 비동기메서드 작업을 실행한다. 근데 이 비동기 메서드 작업을 await 없이 호출하게 되면 작업이 끝나지 않은 채로 다음 코드로 넘어가버리게 되는데 만약에 이 작업의 결과를 참조하는 어떤 값에 접근하게 되면 현재 스레드가 런타임 중에 멈춰버릴 수 있다.
2. 동시성 문제
문제가 하나 더 있다. 바로 DbContext 인스턴스는 여러 스레드의 사용을 못하게 막는다는 것이다. 예를 들어서 호출 스레드에서 해당 dbcontext 인스턴스를 쓰다가 비동기 메서드로 다른 스레드에서 또 쓰려고 하는데 거기서 dbContext를 쓴다고 쳐보자. 그럼 그 비동기작업으로 실행하는 다른 스레드에서 그 dbcontext 인스턴스를 쓸건데 그걸 await 없이 해버린다? 그럼 dbcontext 인스턴스에 다른 스레드가 접근하고 있는 도중에 다음 코드에서 또 접근할 수 있다. 그럼 그렇게 되면 중복 접근이 되버린다. dbcontext 인스턴스는 여러 스레드의 동시접근을 허용하지 않는다.
근데 예를 들어서 나는 스레드 여러개를 가져다가 빠르게 같이 돌리고 싶은데?
그러면 인스턴스를 하나 더 생성해서 따로 쓰라고 문서에서 말한다.
그럼 올바르게 쓴다면?
1. 비동기 메서드 사용시에 await을 꼭 사용한다.
모든 비동기 메서드 호출 시에는 await 키워드를 사용하여 해당 작업이 완료될 때까지 기다려야 한다. 그렇게 해야 현재 스레드를 효율적으로 사용하고 비동기 작업의 완료를 올바르게 처리할 수 있다.
2. 비동기 작업을 수행할 때는 항상 예외처리를 고려해야한다. try-catch 블록을 사용하여 비동기 작업 중 발생할 수 있는 예외를 적절히 처리할 수 있다.