HttpClient 에러?

유닛 테스트를 하다가 아래와 같은 예외가 발생했습니다.

Operations that change non-concurrent collections must have exclusive access. A concurrent update was performed on this collection and corrupted its state. The collection’s state is no longer correct.

관련해서 구글에 검색해보니 원인이 명확하지 않은 것 같습니다.
문제가 발생한 유닛테스트 메서드는 아래와 같습니다. NUnit을 통해 작성되었고, 처음보는 클래스는 자체 제작 클래스라고 보시면 될 듯 합니다.


        [TestCase(419364050, EShopeeItemStatus.NORMAL)]
        [TestCase(419364050, EShopeeItemStatus.UNLIST)]
        public async Task InitGmarketWhiteList(long shopId, EShopeeItemStatus eShopeeItemStatus)
        {
            RequestGetItemList req = new()
            {
                ShopId = shopId,
                PartnerId = _shopeeOption.PartnerId,
                Offset = 0,
                PageSize = 100,
                UpdateTimeFrom = new DateTimeOffset(DateTime.Now.AddYears(-3)).ToUnixTimeSeconds(),
                UpdateTimeTo = new DateTimeOffset(DateTime.Now.AddDays(1)).ToUnixTimeSeconds(),
                ItemStatus = eShopeeItemStatus
            };
            TaskBase<ResponseGetItemBaseInfo> taskBaseRes = await req.FullBaseInfoScanAsync(_shopeeClient);

            if (!taskBaseRes.IsSuccess)
            {
                Assert.False(!taskBaseRes.IsSuccess);
            }

            ResponseGetItemBaseInfo res = taskBaseRes.Result;
            AwesomeSwagClient awesomeSwagClient = new(new HttpClient(), _companyId);
            SemaphoreSlim semaphore = new(20);
            List<Task> tasks = new();

            foreach (ResponseGetItemBaseInfo._Item item in res.ItemList)
            {
                tasks.Add(Task.Run(async () =>
                {
                    await semaphore.WaitAsync();

                    try
                    {
                        Product awesomeSwagSourceItem = await awesomeSwagClient.GetProductFromOrgIdDetailAsync(item.ItemSku);

                        if (awesomeSwagSourceItem is null)
                        {
                            EcWhiteList reqWhiteList = new()
                            {
                                Platform = EPlatform.SHOPEE,
                                PlatformId = item.ItemId.ToString(),
                                ShopId = shopId.ToString(),
                                ShopName = string.Empty,
                            };
                            await _ecWhiteListService.InsertAsync(reqWhiteList); // 예외 발생지점
                        }
                    }
                    finally
                    {
                        semaphore.Release();
                    }
                }));
            }

            await Task.WhenAll(tasks);

            Assert.True(true);
        }

        public async Task InsertAsync(EcWhiteList edata)
        {
            try
            {
                _context.EcWhiteLists.Add(edata);

                await _context.SaveChangesAsync();
            }
            catch (Exception ex)
            {
            }
            
        }

문제가 발생한 메서드는 주석으로 표기했고 메서드의 정의는 아래 InsertAsync 메서드입니다.
Cosmos DB에 Semaphore를 활용하여 웹통신한 결과를 저장하는 코드인데 이런 에러가 발생합니다…
물론 병렬처리없이 동기식으로 foreach문에서 하나씩 돌리면 문제가 발생하지 않습니다.

왜 발생하는 걸까요?

좋아요 2

DbContext의 스레딩 문제로 보입니다.

좋아요 5

@dimohy 님과 같은 의견입니다.

EntityFramework는 Thread-safety로 디자인되지 않았습니다.

SemaphoreSlim을 생성할때 생성자의 인자로 (1, 1)을 넣어보세요.
아마 해당 장애가 없어질거 같습니다.

좋아요 4

아하…그럼 AddRange 형태로 해야겠네요…ㅎㅎ 감사합니다!!

좋아요 1

답변 감사드립니다!! 코드 수정해야겠네요 ㅎㅎ

좋아요 1

EF의 탈을 쓴, 단순한 컬렉션의 thread-not-safe 문제입니다. 예를 들어, 다음과 같은 식으로도 간단하게 재현을 할 수 있습니다.

static async Task Main(string[] args)
{
    Dictionary<int, int> dict = new Dictionary<int, int>(1000000);
    List<Task> tasks = new List<Task>();

    for (int i = 0; i < 2; i++)
    {
        Task task = Task.Run(() => 
        {
            for (int j = 0; j < 1000000; j++)
            {
                dict[j] = j;
            }
        });
        tasks.Add(task);
    }

    await Task.WhenAll(tasks);
}
좋아요 6

예제 코드 감사합니다 ㅎㅎ 동기화가 처리되지 않은 컬렉션 객체에 대해서는 멀티스레딩하면서 수정하면 안된다는 것이군요.

좋아요 2